Skip to content

Commit 17f5371

Browse files
authored
Merge pull request #184 from alison985/154_add_to_dashboard_from_query
add ability to add query to dashboard from query page
2 parents 75584c0 + 780ac40 commit 17f5371

File tree

9 files changed

+155
-3
lines changed

9 files changed

+155
-3
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<div class="modal-header">
2+
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
3+
<h4 class="modal-title">Add to Dashboard</h4>
4+
</div>
5+
<div class="modal-body">
6+
7+
<form name="alertForm" class="form">
8+
<div class="form-group">
9+
<!-- <label>Limit Dashboard search to dashboards I've created? </label>
10+
<input type="checkbox" ng-model="$ctrl.limitToUsersDashboards" on-change="$ctrl.dashboardList == $select.search" ng-disabled="$ctrl.saveInProgress" /> -->
11+
<label>Choose the dashboard to add this query to:</label>
12+
<ui-select ng-model="$ctrl.dashboardList" reset-search-input="false" on-select="$ctrl.onDashboardSelected($item)" ng-disabled="$ctrl.saveInProgress">
13+
<ui-select-match placeholder="Search a dashboard by name">{{$select.selected.name}}</ui-select-match>
14+
<ui-select-choices repeat="q in $ctrl.dashboards"
15+
refresh="$ctrl.searchDashboards($select.search, $ctrl.limitToUsersDashboards)"
16+
refresh-delay="0">
17+
<div ng-bind-html="$ctrl.trustAsHtml(q.name | highlight: $select.search)"></div>
18+
</ui-select-choices>
19+
</ui-select>
20+
</div>
21+
</form>
22+
23+
</div>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import template from './add-to-dashboard.html';
2+
3+
const AddToDashboardForm = {
4+
controller($sce, Dashboard, currentUser, toastr, Query, Widget) {
5+
'ngInject';
6+
7+
this.query = this.resolve.query;
8+
this.vis = this.resolve.vis;
9+
this.saveAddToDashbosard = this.resolve.saveAddToDashboard;
10+
this.saveInProgress = false;
11+
12+
this.trustAsHtml = html => $sce.trustAsHtml(html);
13+
14+
this.onDashboardSelected = (dash) => {
15+
// add widget to dashboard
16+
this.saveInProgress = true;
17+
this.widgetSize = 1;
18+
this.selectedVis = null;
19+
this.query = {};
20+
this.selected_query = this.query.id;
21+
this.type = 'visualization';
22+
this.isVisualization = () => this.type === 'visualization';
23+
24+
const widget = new Widget({
25+
visualization_id: this.vis && this.vis.id,
26+
dashboard_id: dash.id,
27+
options: {},
28+
width: this.widgetSize,
29+
type: this.type,
30+
});
31+
32+
// (response)
33+
widget.$save().then(() => {
34+
// (dashboard)
35+
this.selectedDashboard = Dashboard.get({ slug: dash.slug }, () => {});
36+
this.close();
37+
}).catch(() => {
38+
toastr.error('Widget can not be added');
39+
}).finally(() => {
40+
this.saveInProgress = false;
41+
});
42+
};
43+
44+
this.selectedDashboard = null;
45+
46+
this.searchDashboards = (term) => { // , limitToUsersDashboards
47+
if (!term || term.length < 3) {
48+
return;
49+
}
50+
51+
Dashboard.search({
52+
q: term,
53+
user_id: currentUser.id,
54+
// limit_to_users_dashboards: limitToUsersDashboards,
55+
include_drafts: true,
56+
}, (results) => {
57+
this.dashboards = results;
58+
});
59+
};
60+
},
61+
bindings: {
62+
resolve: '<',
63+
close: '&',
64+
dismiss: '&',
65+
vis: '<',
66+
},
67+
template,
68+
};
69+
70+
export default function (ngModule) {
71+
ngModule.component('addToDashboardDialog', AddToDashboardForm);
72+
}

client/app/pages/queries/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import registerQuerySearchResultsPage from './queries-search-results-page';
1111
import registerVisualizationEmbed from './visualization-embed';
1212
import registerCompareQueryDialog from './compare-query-dialog';
1313
import registerGetDataSourceVersion from './get-data-source-version';
14+
import registerAddToDashboard from './add-to-dashboard';
1415

1516
export default function (ngModule) {
1617
registerQueryResultsLink(ngModule);
@@ -24,6 +25,7 @@ export default function (ngModule) {
2425
registerApiKeyDialog(ngModule);
2526
registerCompareQueryDialog(ngModule);
2627
registerGetDataSourceVersion(ngModule);
28+
registerAddToDashboard(ngModule);
2729

2830
return Object.assign({}, registerQuerySearchResultsPage(ngModule),
2931
registerSourceView(ngModule),

client/app/pages/queries/query.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ <h3>
253253
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" ng-if="vis.type!='TABLE'" base-path="query.getUrl(sourceMode)"
254254
ng-repeat="vis in query.visualizations">
255255
<span class="remove" ng-click="deleteVisualization($event, vis)"
256-
ng-show="canEdit"> &times;</span>
256+
ng-show="canEdit"> &times;</span> <span class="btn btn-xs btn-success" ng-click="openAddToDashboardForm(vis)"> +</span>
257257
</rd-tab>
258258
<li class="rd-tab"><a ng-click="openVisualizationEditor()" ng-if="sourceMode && canEdit">&plus; New Visualization</a></li>
259259
</ul>

client/app/pages/queries/view.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,18 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $location, $window,
362362
});
363363
};
364364

365+
$scope.openAddToDashboardForm = (vis) => {
366+
$uibModal.open({
367+
component: 'addToDashboardDialog',
368+
size: 'sm',
369+
resolve: {
370+
query: $scope.query,
371+
vis,
372+
saveAddToDashboard: () => $scope.saveAddToDashboard,
373+
},
374+
});
375+
};
376+
365377
$scope.showEmbedDialog = (query, visualization) => {
366378
$uibModal.open({
367379
component: 'embedCodeDialog',

client/app/services/dashboard.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ function Dashboard($resource, $http, currentUser, Widget) {
1919
get: { method: 'GET', transformResponse: transform },
2020
save: { method: 'POST', transformResponse: transform },
2121
query: { method: 'GET', isArray: true, transformResponse: transform },
22+
search: { method: 'GET', isArray: true, url: 'api/dashboards/search' },
2223
recent: {
2324
method: 'get',
2425
isArray: true,

redash/handlers/api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from redash.handlers.base import org_scoped_rule
77
from redash.handlers.permissions import ObjectPermissionsListResource, CheckPermissionResource
88
from redash.handlers.alerts import AlertResource, AlertListResource, AlertSubscriptionListResource, AlertSubscriptionResource
9-
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource, PublicDashboardResource
9+
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource, PublicDashboardResource, SearchDashboardResource
1010
from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource, DataSourcePauseResource, DataSourceTestResource, DataSourceVersionResource
1111
from redash.handlers.events import EventResource
1212
from redash.handlers.queries import (
@@ -52,6 +52,7 @@ def json_representation(data, code, headers=None):
5252
api.add_org_resource(DashboardResource, '/api/dashboards/<dashboard_slug>', endpoint='dashboard')
5353
api.add_org_resource(PublicDashboardResource, '/api/dashboards/public/<token>', endpoint='public_dashboard')
5454
api.add_org_resource(DashboardShareResource, '/api/dashboards/<dashboard_id>/share', endpoint='dashboard_share')
55+
api.add_org_resource(SearchDashboardResource, '/api/dashboards/search')
5556

5657
api.add_org_resource(DataSourceTypeListResource, '/api/data_sources/types', endpoint='data_source_types')
5758
api.add_org_resource(DataSourceListResource, '/api/data_sources', endpoint='data_sources')

redash/handlers/dashboards.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def post(self):
6565
models.db.session.commit()
6666
return dashboard.to_dict()
6767

68-
6968
class DashboardResource(BaseResource):
7069
@require_permission('list_dashboards')
7170
def get(self, dashboard_slug=None):
@@ -241,3 +240,21 @@ def delete(self, dashboard_id):
241240
'object_id': dashboard.id,
242241
'object_type': 'dashboard',
243242
})
243+
244+
class SearchDashboardResource(BaseResource):
245+
@require_permission('list_dashboards')
246+
def get(self):
247+
"""
248+
Searches for a dashboard.
249+
250+
Sends to models.py > Dashboard > search()
251+
search(cls, term, user_id, group_ids, limit_to_users_dashboards=False, include_drafts=False)
252+
"""
253+
term = request.args.get('q', '')
254+
include_drafts = request.args.get('include_drafts') is not None
255+
user_id = request.args.get('user_id', '')
256+
# limit_to_users_dashboards = request.args.get('limit_to_users_dashboards', '')
257+
258+
# limit_to_users_dashboards=limit_to_users_dashboards,
259+
return [q.to_dict() for q in models.Dashboard.search(term, user_id, self.current_user.group_ids, include_drafts=include_drafts)]
260+

redash/models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,30 @@ def all(cls, org, group_ids, user_id):
13061306

13071307
return query
13081308

1309+
@classmethod
1310+
def search(cls, term, user_id, group_ids, include_drafts=False):
1311+
# limit_to_users_dashboards=False,
1312+
# TODO: This is very naive implementation of search, to be replaced with PostgreSQL full-text-search solution.
1313+
where = (Dashboard.name.ilike(u"%{}%".format(term)))
1314+
1315+
if term.isdigit():
1316+
where |= Dashboard.id == term
1317+
1318+
#if limit_to_users_dashboards:
1319+
# where &= Dashboard.user_id == user_id
1320+
1321+
where &= Dashboard.is_archived == False
1322+
1323+
if not include_drafts:
1324+
where &= Dashboard.is_draft == False
1325+
1326+
where &= DataSourceGroup.group_id.in_(group_ids)
1327+
dashboard_ids = (
1328+
db.session.query(Dashboard.id)
1329+
.filter(where)).distinct()
1330+
1331+
return Dashboard.query.filter(Dashboard.id.in_(dashboard_ids))
1332+
13091333
@classmethod
13101334
def recent(cls, org, group_ids, user_id, for_user=False, limit=20):
13111335
query = (Dashboard.query

0 commit comments

Comments
 (0)