Implement empty states logic.

This commit is contained in:
Arik Fraimovich 2018-02-22 11:02:12 +02:00
parent 4d44be76ac
commit 3a840fcc5d
12 changed files with 163 additions and 60 deletions

View File

@ -1,18 +1,29 @@
<div class="empty-state bg-white tiled">
<div class="empty-state__summary">
<h4 ng-if="$ctrl.title">{{$ctrl.title}}</h4>
<h2 ng-if="$ctrl.icon"><i ng-class="$ctrl.icon" aria-hidden="true"></i></h2>
<p>{{$ctrl.description}}</p>
</div>
<div class="empty-state__steps">
<h4>Let's get started</h4>
<ol>
<li><del><a href="/data_sources">Connect</a> a Data Source</del></li>
<li><del><a href="/queries/new/">Create</a> your first Query</del></li>
<li ng-if="$ctrl.showAlertStep"><a href="/alerts/new/">Create</a> your first Alert</li>
<li ng-if="$ctrl.showDashboardStep"><a href="/alerts/new/">Create</a> your first Dashboard</li>
<li ng-if="$ctrl.showInviteStep"><a href="/alerts/new/">Invite</a> your team members</li>
</ol>
<p>Need more support? <a href="{{$ctrl.helpLink}}" target="_blank">See our Help <i class="fa fa-external-link" aria-hidden="true"></i></a></p>
</div>
</div>
<div class="empty-state bg-white tiled" ng-if="!$ctrl.loading && $ctrl.shouldShowOnboarding()">
<div class="empty-state__summary">
<h4 ng-if="$ctrl.title">{{$ctrl.title}}</h4>
<h2 ng-if="$ctrl.icon">
<i ng-class="$ctrl.icon" aria-hidden="true"></i>
</h2>
<p>{{$ctrl.description}}</p>
</div>
<div class="empty-state__steps">
<h4>Let's get started</h4>
<ol>
<li ng-class="{done: $ctrl.dataSourceStepCompleted}">
<a href="/data_sources">Connect</a> a Data Source</li>
<li ng-class="{done: $ctrl.queryStepCompleted}">
<a href="/queries/new">Create</a> your first Query</li>
<li ng-if="$ctrl.showAlertStep" ng-class="{done: $ctrl.alertStepCompleted}">
<a href="/alerts/new">Create</a> your first Alert</li>
<li ng-if="$ctrl.showDashboardStep" ng-class="{done: $ctrl.dashboardStepCompleted}">
<a ng-click="$ctrl.newDashboard()">Create</a> your first Dashboard</li>
<li ng-if="$ctrl.showInviteStep" ng-class="{done: $ctrl.inviteStepCompleted}">
<a href="/users/new">Invite</a> your team members</li>
</ol>
<p>Need more support?
<a href="{{$ctrl.helpLink}}" target="_blank">See our Help
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
</p>
</div>
</div>

View File

@ -12,8 +12,47 @@ const EmptyStateComponent = {
showAlertStep: '<',
showDashboardStep: '<',
showInviteStep: '<',
onboardingMode: '<',
},
controller() {
controller($http, $uibModal) {
this.loading = true;
$http.get('api/organization/status').then((response) => {
this.loading = false;
const counters = response.data.object_counters;
this.dataSourceStepCompleted = counters.data_sources > 0;
this.queryStepCompleted = counters.queries > 0;
this.dashboardStepCompleted = counters.dashboards > 0;
this.alertStepCompleted = counters.alerts > 0;
this.inviteStepCompleted = counters.users > 1;
});
this.shouldShowOnboarding = () => {
if (this.loading) {
return false;
}
if (!this.onboardingMode) {
return true;
}
return !(
this.dataSourceStepCompleted &&
this.queryStepCompleted &&
this.dashboardStepCompleted &&
this.inviteStepCompleted
);
};
this.newDashboard = () => {
$uibModal.open({
component: 'editDashboardDialog',
resolve: {
dashboard: () => ({ name: null, layout: null }),
},
});
};
},
};

View File

@ -9,7 +9,8 @@
font-size: 14px;
line-height: 21px;
.empty-state__summary, .empty-state__steps {
.empty-state__summary,
.empty-state__steps {
width: 48%;
}
@ -22,6 +23,10 @@
padding: 17px;
}
li.done {
text-decoration: line-through;
}
h2 {
margin: 0 0 15px;
}
@ -43,7 +48,8 @@
margin-bottom: 25px;
}
.empty-state__summary, .empty-state__steps {
.empty-state__summary,
.empty-state__steps {
width: 100%;
}
}

View File

@ -4,9 +4,10 @@
<empty-state icon="fa fa-bell-o"
description="Get notified on certain events"
show-alert-step="true"
help-link="http://help.redash.io/category/23-alerts"></empty-state>
help-link="http://help.redash.io/category/23-alerts"
ng-if="$ctrl.showEmptyState"></empty-state>
<div class="bg-white tiled">
<div class="bg-white tiled" ng-if="$ctrl.showList">
<table class="table table-condensed table-hover">
<thead>
<tr>

View File

@ -11,8 +11,17 @@ class AlertsListCtrl {
constructor(Events, Alert) {
Events.record('view', 'page', 'alerts');
this.showEmptyState = false;
this.showList = false;
this.alerts = new Paginator([], { itemsPerPage: 20 });
Alert.query((alerts) => {
if (alerts.length > 0) {
this.showList = true;
} else {
this.showEmptyState = true;
}
this.alerts.updateRows(alerts.map(alert => ({
id: alert.id,
name: alert.name,

View File

@ -4,9 +4,10 @@
<empty-state icon="zmdi zmdi-view-quilt"
description="See the big picture"
show-dashboard-step="true"
help-link="http://help.redash.io/category/22-dashboards"></empty-state>
help-link="http://help.redash.io/category/22-dashboards"
ng-if="$ctrl.showEmptyState"></empty-state>
<div class="row">
<div class="row" ng-if="$ctrl.showList">
<div class="col-lg-3">
<input type='text' class='form-control' placeholder="Search Dashboards..."
ng-change="$ctrl.update()" ng-model="$ctrl.searchText" autofocus/>

View File

@ -35,7 +35,15 @@ function DashboardListCtrl(Dashboard, $location) {
};
this.allTags = [];
this.showList = false;
this.showEmptyState = false;
this.dashboards.$promise.then((data) => {
if (data.length > 0) {
this.showList = true;
} else {
this.showEmptyState = true;
}
const out = data.map(dashboard => dashboard.name.match(TAGS_REGEX));
this.allTags = _.unique(_.flatten(out)).filter(e => e).map(tag => tag.replace(/:$/, ''));
this.allTags.sort();

View File

@ -1,10 +1,9 @@
<div class="container">
<empty-state title="Welcome to Redash 👋"
description="Connect to any data source, easily visualize and share your data"
show-dashboard-step="true"
show-invite-step="true"
onboarding-mode="true"
help-link="http://help.redash.io/article/32-getting-started"></empty-state>
<div class="tile">

View File

@ -50,6 +50,9 @@ class QueriesListCtrl {
{ name: 'My Queries', path: 'queries/my' },
{ name: 'Search', path: 'queries/search' },
];
this.showList = () => this.paginator.getPageRows() !== undefined && this.paginator.getPageRows().length > 0;
this.showEmptyState = () => this.paginator.getPageRows() !== undefined && this.paginator.getPageRows().length === 0;
}
}

View File

@ -1,38 +1,41 @@
<div class="container">
<page-header title="Queries"></page-header>
<empty-state icon="fa fa-code"
description="Getting the data from your datasources."
help-link="http://help.redash.io/category/21-querying"></empty-state>
<empty-state icon="fa fa-code" description="Getting the data from your datasources." help-link="http://help.redash.io/category/21-querying"
ng-if="$ctrl.showEmptyState()"></empty-state>
<tab-nav tabs="$ctrl.tabs"></tab-nav>
<div ng-if="$ctrl.showList()">
<tab-nav tabs="$ctrl.tabs"></tab-nav>
<div class="bg-white tiled">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Created By</th>
<th>Created At</th>
<th>Runtime</th>
<th>Last Executed At</th>
<th>Update Schedule</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="query in $ctrl.paginator.getPageRows()">
<td><a href="queries/{{query.id}}">{{query.name}}</a> <span class="label label-default" ng-if="query.is_draft">Unpublished</span></td>
<td>
<img ng-src="{{query.user.profile_image_url}}" class="profile__image_thumb"/>
{{query.user.name}}
</td>
<td>{{query.created_at | dateTime}}</td>
<td>{{query.runtime | durationHumanize}}</td>
<td>{{query.retrieved_at | dateTime}}</td>
<td>{{query.schedule | scheduleHumanize}}</td>
</tr>
</tbody>
</table>
<paginator paginator="$ctrl.paginator"></paginator>
<div class="bg-white tiled">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Created By</th>
<th>Created At</th>
<th>Runtime</th>
<th>Last Executed At</th>
<th>Update Schedule</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="query in $ctrl.paginator.getPageRows()">
<td>
<a href="queries/{{query.id}}">{{query.name}}</a>
<span class="label label-default" ng-if="query.is_draft">Unpublished</span>
</td>
<td>
<img ng-src="{{query.user.profile_image_url}}" class="profile__image_thumb" /> {{query.user.name}}
</td>
<td>{{query.created_at | dateTime}}</td>
<td>{{query.runtime | durationHumanize}}</td>
<td>{{query.retrieved_at | dateTime}}</td>
<td>{{query.schedule | scheduleHumanize}}</td>
</tr>
</tbody>
</table>
<paginator paginator="$ctrl.paginator"></paginator>
</div>
</div>
</div>
</div>

View File

@ -21,6 +21,6 @@ def status_api():
def init_app(app):
from redash.handlers import embed, queries, static, authentication, admin, setup
from redash.handlers import embed, queries, static, authentication, admin, setup, organization
app.register_blueprint(routes)
api.init_app(app)

View File

@ -0,0 +1,23 @@
import json
from flask import request
from flask_login import login_required
from redash import models
from redash.handlers import routes
from redash.handlers.base import json_response
from redash.permissions import require_admin
@routes.route('/api/organization/status', methods=['GET'])
@require_admin
@login_required
def organization_status():
counters = {
'users': models.User.query.count(),
'alerts': models.Alert.query.count(),
'data_sources': models.DataSource.query.count(),
# todo: not archived
'queries': models.Query.query.count(),
'dashboards': models.Dashboard.query.count(),
}
return json_response(dict(object_counters=counters))