Skip to content

Commit

Permalink
Merge pull request #184 from alison985/154_add_to_dashboard_from_query
Browse files Browse the repository at this point in the history
add ability to add query to dashboard from query page
  • Loading branch information
alison985 authored Aug 10, 2017
2 parents 75584c0 + 780ac40 commit 17f5371
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 3 deletions.
23 changes: 23 additions & 0 deletions client/app/pages/queries/add-to-dashboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="modal-header">
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Add to Dashboard</h4>
</div>
<div class="modal-body">

<form name="alertForm" class="form">
<div class="form-group">
<!-- <label>Limit Dashboard search to dashboards I've created? </label>
<input type="checkbox" ng-model="$ctrl.limitToUsersDashboards" on-change="$ctrl.dashboardList == $select.search" ng-disabled="$ctrl.saveInProgress" /> -->
<label>Choose the dashboard to add this query to:</label>
<ui-select ng-model="$ctrl.dashboardList" reset-search-input="false" on-select="$ctrl.onDashboardSelected($item)" ng-disabled="$ctrl.saveInProgress">
<ui-select-match placeholder="Search a dashboard by name">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="q in $ctrl.dashboards"
refresh="$ctrl.searchDashboards($select.search, $ctrl.limitToUsersDashboards)"
refresh-delay="0">
<div ng-bind-html="$ctrl.trustAsHtml(q.name | highlight: $select.search)"></div>
</ui-select-choices>
</ui-select>
</div>
</form>

</div>
72 changes: 72 additions & 0 deletions client/app/pages/queries/add-to-dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import template from './add-to-dashboard.html';

const AddToDashboardForm = {
controller($sce, Dashboard, currentUser, toastr, Query, Widget) {
'ngInject';

this.query = this.resolve.query;
this.vis = this.resolve.vis;
this.saveAddToDashbosard = this.resolve.saveAddToDashboard;
this.saveInProgress = false;

this.trustAsHtml = html => $sce.trustAsHtml(html);

this.onDashboardSelected = (dash) => {
// add widget to dashboard
this.saveInProgress = true;
this.widgetSize = 1;
this.selectedVis = null;
this.query = {};
this.selected_query = this.query.id;
this.type = 'visualization';
this.isVisualization = () => this.type === 'visualization';

const widget = new Widget({
visualization_id: this.vis && this.vis.id,
dashboard_id: dash.id,
options: {},
width: this.widgetSize,
type: this.type,
});

// (response)
widget.$save().then(() => {
// (dashboard)
this.selectedDashboard = Dashboard.get({ slug: dash.slug }, () => {});
this.close();
}).catch(() => {
toastr.error('Widget can not be added');
}).finally(() => {
this.saveInProgress = false;
});
};

this.selectedDashboard = null;

this.searchDashboards = (term) => { // , limitToUsersDashboards
if (!term || term.length < 3) {
return;
}

Dashboard.search({
q: term,
user_id: currentUser.id,
// limit_to_users_dashboards: limitToUsersDashboards,
include_drafts: true,
}, (results) => {
this.dashboards = results;
});
};
},
bindings: {
resolve: '<',
close: '&',
dismiss: '&',
vis: '<',
},
template,
};

export default function (ngModule) {
ngModule.component('addToDashboardDialog', AddToDashboardForm);
}
2 changes: 2 additions & 0 deletions client/app/pages/queries/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import registerQuerySearchResultsPage from './queries-search-results-page';
import registerVisualizationEmbed from './visualization-embed';
import registerCompareQueryDialog from './compare-query-dialog';
import registerGetDataSourceVersion from './get-data-source-version';
import registerAddToDashboard from './add-to-dashboard';

export default function (ngModule) {
registerQueryResultsLink(ngModule);
Expand All @@ -24,6 +25,7 @@ export default function (ngModule) {
registerApiKeyDialog(ngModule);
registerCompareQueryDialog(ngModule);
registerGetDataSourceVersion(ngModule);
registerAddToDashboard(ngModule);

return Object.assign({}, registerQuerySearchResultsPage(ngModule),
registerSourceView(ngModule),
Expand Down
2 changes: 1 addition & 1 deletion client/app/pages/queries/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ <h3>
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" ng-if="vis.type!='TABLE'" base-path="query.getUrl(sourceMode)"
ng-repeat="vis in query.visualizations">
<span class="remove" ng-click="deleteVisualization($event, vis)"
ng-show="canEdit"> &times;</span>
ng-show="canEdit"> &times;</span> <span class="btn btn-xs btn-success" ng-click="openAddToDashboardForm(vis)"> +</span>
</rd-tab>
<li class="rd-tab"><a ng-click="openVisualizationEditor()" ng-if="sourceMode && canEdit">&plus; New Visualization</a></li>
</ul>
Expand Down
12 changes: 12 additions & 0 deletions client/app/pages/queries/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,18 @@ function QueryViewCtrl($scope, Events, $route, $routeParams, $location, $window,
});
};

$scope.openAddToDashboardForm = (vis) => {
$uibModal.open({
component: 'addToDashboardDialog',
size: 'sm',
resolve: {
query: $scope.query,
vis,
saveAddToDashboard: () => $scope.saveAddToDashboard,
},
});
};

$scope.showEmbedDialog = (query, visualization) => {
$uibModal.open({
component: 'embedCodeDialog',
Expand Down
1 change: 1 addition & 0 deletions client/app/services/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function Dashboard($resource, $http, currentUser, Widget) {
get: { method: 'GET', transformResponse: transform },
save: { method: 'POST', transformResponse: transform },
query: { method: 'GET', isArray: true, transformResponse: transform },
search: { method: 'GET', isArray: true, url: 'api/dashboards/search' },
recent: {
method: 'get',
isArray: true,
Expand Down
3 changes: 2 additions & 1 deletion redash/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from redash.handlers.base import org_scoped_rule
from redash.handlers.permissions import ObjectPermissionsListResource, CheckPermissionResource
from redash.handlers.alerts import AlertResource, AlertListResource, AlertSubscriptionListResource, AlertSubscriptionResource
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource, PublicDashboardResource
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource, PublicDashboardResource, SearchDashboardResource
from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource, DataSourcePauseResource, DataSourceTestResource, DataSourceVersionResource
from redash.handlers.events import EventResource
from redash.handlers.queries import (
Expand Down Expand Up @@ -52,6 +52,7 @@ def json_representation(data, code, headers=None):
api.add_org_resource(DashboardResource, '/api/dashboards/<dashboard_slug>', endpoint='dashboard')
api.add_org_resource(PublicDashboardResource, '/api/dashboards/public/<token>', endpoint='public_dashboard')
api.add_org_resource(DashboardShareResource, '/api/dashboards/<dashboard_id>/share', endpoint='dashboard_share')
api.add_org_resource(SearchDashboardResource, '/api/dashboards/search')

api.add_org_resource(DataSourceTypeListResource, '/api/data_sources/types', endpoint='data_source_types')
api.add_org_resource(DataSourceListResource, '/api/data_sources', endpoint='data_sources')
Expand Down
19 changes: 18 additions & 1 deletion redash/handlers/dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def post(self):
models.db.session.commit()
return dashboard.to_dict()


class DashboardResource(BaseResource):
@require_permission('list_dashboards')
def get(self, dashboard_slug=None):
Expand Down Expand Up @@ -241,3 +240,21 @@ def delete(self, dashboard_id):
'object_id': dashboard.id,
'object_type': 'dashboard',
})

class SearchDashboardResource(BaseResource):
@require_permission('list_dashboards')
def get(self):
"""
Searches for a dashboard.
Sends to models.py > Dashboard > search()
search(cls, term, user_id, group_ids, limit_to_users_dashboards=False, include_drafts=False)
"""
term = request.args.get('q', '')
include_drafts = request.args.get('include_drafts') is not None
user_id = request.args.get('user_id', '')
# limit_to_users_dashboards = request.args.get('limit_to_users_dashboards', '')

# limit_to_users_dashboards=limit_to_users_dashboards,
return [q.to_dict() for q in models.Dashboard.search(term, user_id, self.current_user.group_ids, include_drafts=include_drafts)]

24 changes: 24 additions & 0 deletions redash/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,30 @@ def all(cls, org, group_ids, user_id):

return query

@classmethod
def search(cls, term, user_id, group_ids, include_drafts=False):
# limit_to_users_dashboards=False,
# TODO: This is very naive implementation of search, to be replaced with PostgreSQL full-text-search solution.
where = (Dashboard.name.ilike(u"%{}%".format(term)))

if term.isdigit():
where |= Dashboard.id == term

#if limit_to_users_dashboards:
# where &= Dashboard.user_id == user_id

where &= Dashboard.is_archived == False

if not include_drafts:
where &= Dashboard.is_draft == False

where &= DataSourceGroup.group_id.in_(group_ids)
dashboard_ids = (
db.session.query(Dashboard.id)
.filter(where)).distinct()

return Dashboard.query.filter(Dashboard.id.in_(dashboard_ids))

@classmethod
def recent(cls, org, group_ids, user_id, for_user=False, limit=20):
query = (Dashboard.query
Expand Down

0 comments on commit 17f5371

Please sign in to comment.