Skip to content

Commit

Permalink
add ability to add query to dashboard from query page (re #154)
Browse files Browse the repository at this point in the history
  • Loading branch information
alison985 authored and Allen Short committed Jul 25, 2018
1 parent 8ede898 commit f601211
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 5 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>
70 changes: 70 additions & 0 deletions client/app/pages/queries/add-to-dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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.get({
q: term,
include_drafts: true,
}, (results) => {
this.dashboards = results.results;
});
};
},
bindings: {
resolve: '<',
close: '&',
dismiss: '&',
vis: '<',
},
template,
};

export default function (ngModule) {
ngModule.component('addToDashboardDialog', AddToDashboardForm);
}
1 change: 1 addition & 0 deletions client/app/pages/queries/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ <h3>
<span class="remove" ng-click="deleteVisualization($event, vis)" ng-if="canEdit && !($first && (vis.type === 'TABLE'))">
<i class="zmdi zmdi-close"></i>
</span>
<span class="btn btn-xs btn-success" ng-click="openAddToDashboardForm(vis)"> +</span>
</rd-tab>
<li class="rd-tab tab-new-vis">
<a ng-click="openVisualizationEditor()" class="btn btn-default" ng-if="canEdit">
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 @@ -455,6 +455,18 @@ function QueryViewCtrl(
});
};

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

$scope.showEmbedDialog = (query, visId) => {
const visualization = getVisualization(visId);
$uibModal.open({
Expand Down
2 changes: 1 addition & 1 deletion redash/handlers/dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def get(self):
search_term = request.args.get('q')

if search_term:
results = models.Dashboard.search(self.current_org, self.current_user.group_ids, self.current_user.id, search_term)
results = models.Dashboard.search(self.current_org, self.current_user.group_ids, self.current_user.id, search_term, 'include_drafts' in request.args)
else:
results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)

Expand Down
8 changes: 4 additions & 4 deletions redash/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,7 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model
}

@classmethod
def all(cls, org, group_ids, user_id):
def all(cls, org, group_ids, user_id, include_drafts=False):
query = (
Dashboard.query
.options(joinedload(Dashboard.user))
Expand All @@ -1359,14 +1359,14 @@ def all(cls, org, group_ids, user_id):
Dashboard.org == org)
.distinct())

query = query.filter(or_(Dashboard.user_id == user_id, Dashboard.is_draft == False))
query = query.filter(or_(Dashboard.user_id == user_id, Dashboard.is_draft == include_drafts))

return query

@classmethod
def search(cls, org, groups_ids, user_id, search_term):
def search(cls, org, groups_ids, user_id, search_term, include_drafts):
# TODO: switch to FTS
return cls.all(org, groups_ids, user_id).filter(cls.name.ilike('%{}%'.format(search_term)))
return cls.all(org, groups_ids, user_id, include_drafts).filter(cls.name.ilike('%{}%'.format(search_term)))

@classmethod
def all_tags(cls, org, user):
Expand Down
40 changes: 40 additions & 0 deletions tests/handlers/test_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
from redash.serializers import serialize_dashboard


class TestRecentDashboardResourceGet(BaseTestCase):
def test_get_recent_dashboard_list_does_not_include_deleted(self):
d1 = self.factory.create_dashboard()
expected = d1.to_dict()
d2 = self.factory.create_dashboard() # this shouldn't be required but test fails without it
rv = self.make_request('post', '/api/dashboards/{0}'.format(d1.id),
data={'name': 'New Name', 'layout': '[]', 'is_archived': True})
rvrecent = self.make_request('get', '/api/dashboards/recent')
self.assertEquals(rvrecent.status_code, 200)
actual = json.loads(rvrecent.data)
self.assertNotIn(expected['id'], actual)

class TestDashboardListResource(BaseTestCase):
def test_create_new_dashboard(self):
dashboard_name = 'Test Dashboard'
Expand Down Expand Up @@ -182,3 +194,31 @@ def test_requires_admin_or_owner(self):

res = self.make_request('delete', '/api/dashboards/{}/share'.format(dashboard.id), user=user)
self.assertEqual(res.status_code, 200)

class TestDashboardSearchResourceGet(BaseTestCase):
def create_dashboard_sequence(self):
d1 = self.factory.create_dashboard()
new_name = 'Analytics'
rv1 = self.make_request('post', '/api/dashboards/{0}'.format(d1.id),
data={'name': new_name, 'layout': '[]', 'is_draft': False})
d2 = self.factory.create_dashboard()
rv2 = self.make_request('post', '/api/dashboards/{0}'.format(d2.id),
data={'name': 'Metrics', 'layout': '[]', 'is_draft': True})
user = self.factory.create_user()
return d1, d2, user

def test_get_dashboard_search_results_does_not_contain_deleted(self):
d1, d2, user = self.create_dashboard_sequence()
res = self.make_request('delete', '/api/dashboards/{}/share'.format(d2.id))
dash_search_list = self.make_request('get','/api/dashboards/search?q=Metrics')
dash_search_list_json = json.loads(dash_search_list.data)
self.assertNotIn(d2.id, dash_search_list_json)

def test_get_dashboard_search_results_obeys_draft_flag(self):
d1, d2, user = self.create_dashboard_sequence()
dash_search_list = self.make_request('get','/api/dashboards/search?q=Metrics&test=True&user_id={}'.format(user.id))
dash_search_list_json = json.loads(dash_search_list.data)
self.assertNotIn(d2.id, dash_search_list_json)
#self.assertIn(d1.id, dash_search_list_json)


12 changes: 12 additions & 0 deletions tests/handlers/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,15 @@ def test_delete_widget(self):
self.assertEquals(rv.status_code, 200)
dashboard = models.Dashboard.get_by_slug_and_org(widget.dashboard.slug, widget.dashboard.org)
self.assertEquals(dashboard.widgets.count(), 0)

def test_updates_textbox_widget(self):
widget = self.factory.create_widget()

rv = self.make_request('post', '/api/widgets/{0}'.format(widget.id), data={'width':2,'text':'sing and shine on', 'options': {}})

self.assertEquals(rv.status_code, 200)
dashboard = models.Dashboard.get_by_slug_and_org(widget.dashboard.slug, widget.dashboard.org)
self.assertEquals(dashboard.widgets.count(), 1)
self.assertEquals(dashboard.layout, '[]')


0 comments on commit f601211

Please sign in to comment.