Skip to content

Commit

Permalink
Merge pull request #1353 from washort/draft-toggle
Browse files Browse the repository at this point in the history
Change: make draft status for queries and dashboards toggleable
  • Loading branch information
arikfr authored Nov 22, 2016
2 parents 265c973 + 65a6385 commit 971f961
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 35 deletions.
18 changes: 18 additions & 0 deletions migrations/0027_add_draft_toggle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from playhouse.migrate import PostgresqlMigrator, migrate

from redash.models import db
from redash import models

if __name__ == '__main__':
db.connect_db()
migrator = PostgresqlMigrator(db.database)

with db.database.transaction():
migrate(
migrator.add_column('queries', 'is_draft', models.Query.is_draft)
)
migrate(
migrator.add_column('dashboards', 'is_draft', models.Query.is_draft)
)
db.database.execute_sql("UPDATE queries SET is_draft = (name = 'New Query')")
db.close_db(None)
11 changes: 11 additions & 0 deletions rd_ui/app/scripts/controllers/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@
});
};

$scope.togglePublished = function () {
Events.record(currentUser, "toggle_published", "dashboard", $scope.dashboard.id);
$scope.dashboard.is_draft = !$scope.dashboard.is_draft;
$scope.saveInProgress = true;
Dashboard.save({slug: $scope.dashboard.id, name: $scope.dashboard.name,
layout: JSON.stringify($scope.dashboard.layout),
is_draft: $scope.dashboard.is_draft},
function() {$scope.saveInProgress = false;});

};

$scope.toggleFullscreen = function() {
$scope.isFullscreen = !$scope.isFullscreen;
$('body').toggleClass('headless');
Expand Down
8 changes: 7 additions & 1 deletion rd_ui/app/scripts/controllers/query_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
data.id = $scope.query.id;
data.version = $scope.query.version;
} else {
data = _.pick($scope.query, ["schedule", "query", "id", "description", "name", "data_source_id", "options", "latest_query_data_id", "version"]);
data = _.pick($scope.query, ["schedule", "query", "id", "description", "name", "data_source_id", "options", "latest_query_data_id", "version", "is_draft"]);
}

options = _.extend({}, {
Expand Down Expand Up @@ -163,6 +163,12 @@
$scope.saveQuery(undefined, {'name': $scope.query.name});
};

$scope.togglePublished = function() {
Events.record(currentUser, 'toggle_published', 'query', $scope.query.id);
$scope.query.is_draft = !$scope.query.is_draft;
$scope.saveQuery(undefined, {'is_draft': $scope.query.is_draft});
};

$scope.executeQuery = function() {
if (!$scope.canExecuteQuery()) {
return;
Expand Down
5 changes: 5 additions & 0 deletions rd_ui/app/views/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@
<li><a data-toggle="modal" hash-link hash="edit_dashboard_dialog">Edit Dashboard</a></li>
<li><a data-toggle="modal" hash-link hash="add_query_dialog">Add Widget</a></li>
<li ng-if="showPermissionsControl"><a ng-click="showManagePermissionsModal()">Manage Permissions</a></li>
<li ng-if="!dashboard.is_draft"><a ng-click="togglePublished()">Unpublish Dashboard</a></li>
<li ng-if="dashboard.is_draft"><a ng-click="togglePublished()">Publish Dashboard</a></li>
<li ng-if="!dashboard.is_archived"><a ng-click="archiveDashboard()">Archive Dashboard</a></li>
</ul>
</div>
</page-header>
<div class="col-lg-12 p-5 m-b-10 bg-orange c-white" ng-if="dashboard.is_archived">
This dashboard is archived and won't appear in the dashboards list or search results.
</div>
<div class="col-lg-12 p-5 m-b-10 bg-orange c-white" ng-if="dashboard.is_draft">
This dashboard is a draft.
</div>

<div class="m-b-5">
<filters ng-if="dashboard.dashboard_filters_enabled"></filters>
Expand Down
5 changes: 5 additions & 0 deletions rd_ui/app/views/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ <h3>
<div class="col-lg-12 p-5 bg-orange c-white" ng-if="query.is_archived">
This query is archived and can't be used in dashboards, and won't appear in search results.
</div>
<div class="col-lg-12 p-5 bg-orange c-white" ng-if="query.is_draft">
This query is a draft.
</div>
</div>

<! -- editor -->
Expand Down Expand Up @@ -126,6 +129,8 @@ <h3>
<ul class="dropdown-menu pull-right" dropdown-menu>
<li ng-if="!query.is_archived && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))"><a hash-link hash="archive-confirmation-modal" data-toggle="modal">Archive Query</a></li>
<li ng-if="!query.is_archived && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin')) && showPermissionsControl"><a ng-click="showManagePermissionsModal()">Manage Permissions</a></li>
<li ng-if="query.is_draft && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))"><a ng-click="togglePublished()">Publish Query</a></li>
<li ng-if="!query.is_draft && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))"><a ng-click="togglePublished()">Unpublish Query</a></li>
<li ng-if="query.id != undefined"><a ng-click="showApiKey()">Show API Key</a></li>
</ul>
</div>
Expand Down
16 changes: 8 additions & 8 deletions redash/handlers/dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ def get(self):
@require_permission('create_dashboard')
def post(self):
dashboard_properties = request.get_json(force=True)
dashboard = models.Dashboard.create(name=dashboard_properties['name'],
org=self.current_org,
user=self.current_user,
layout='[]')

result = dashboard.to_dict()
return result
dashboard = models.Dashboard(name=dashboard_properties['name'],
org=self.current_org,
user=self.current_user,
is_draft=True,
layout='[]')
return dashboard.to_dict()


class DashboardResource(BaseResource):
Expand All @@ -63,7 +62,8 @@ def post(self, dashboard_slug):

require_object_modify_permission(dashboard, self.current_user)

updates = project(dashboard_properties, ('name', 'layout', 'version'))
updates = project(dashboard_properties, ('name', 'layout', 'version',
'is_draft'))
updates['changed_by'] = self.current_user

try:
Expand Down
1 change: 1 addition & 0 deletions redash/handlers/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def post(self):
query_def['user'] = self.current_user
query_def['data_source'] = data_source
query_def['org'] = self.current_org
query_def['is_draft'] = True
query = models.Query.create(**query_def)

self.record_event({
Expand Down
63 changes: 38 additions & 25 deletions redash/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ class Query(ChangeTrackingMixin, ModelTimestampsMixin, BaseVersionedModel, Belon
user = peewee.ForeignKeyField(User)
last_modified_by = peewee.ForeignKeyField(User, null=True, related_name="modified_queries")
is_archived = peewee.BooleanField(default=False, index=True)
is_draft = peewee.BooleanField(default=True, index=True)
schedule = peewee.CharField(max_length=10, null=True)
options = JSONField(default={})

Expand All @@ -719,6 +720,7 @@ def to_dict(self, with_stats=False, with_visualizations=False, with_user=True, w
'schedule': self.schedule,
'api_key': self.api_key,
'is_archived': self.is_archived,
'is_draft': self.is_draft,
'updated_at': self.updated_at,
'created_at': self.created_at,
'data_source_id': self.data_source_id,
Expand Down Expand Up @@ -771,10 +773,9 @@ def all_queries(cls, groups, drafts=False):
.order_by(cls.created_at.desc())

if drafts:
q = q.where(Query.name == 'New Query')
q = q.where(Query.is_draft == True)
else:
q = q.where(Query.name != 'New Query')

q = q.where(Query.is_draft == False)
return q

@classmethod
Expand Down Expand Up @@ -818,17 +819,22 @@ def search(cls, term, groups):

@classmethod
def recent(cls, groups, user_id=None, limit=20):
query = cls.select(Query, User).where(Event.created_at > peewee.SQL("current_date - 7")).\
join(Event, on=(Query.id == Event.object_id.cast('integer'))). \
join(DataSourceGroup, on=(Query.data_source==DataSourceGroup.data_source)). \
switch(Query).join(User).\
where(Event.action << ('edit', 'execute', 'edit_name', 'edit_description', 'view_source')).\
where(~(Event.object_id >> None)).\
where(Event.object_type == 'query'). \
where(DataSourceGroup.group << groups).\
where(cls.is_archived == False).\
group_by(Event.object_id, Query.id, User.id).\
order_by(peewee.SQL("count(0) desc"))
query = (
cls.select(Query, User)
.where(Event.created_at > peewee.SQL("current_date - 7"))
.join(Event, on=(Query.id == Event.object_id.cast('integer')))
.join(DataSourceGroup, on=(Query.data_source==DataSourceGroup.data_source))
.switch(Query).join(User)
.where(Event.action << ('edit', 'execute', 'edit_name',
'edit_description', 'toggle_published',
'view_source'))
.where(~(Event.object_id >> None))
.where(Event.object_type == 'query')
.where(DataSourceGroup.group << groups)
.where(cls.is_archived == False)
.where(cls.is_draft == False)
.group_by(Event.object_id, Query.id, User.id)
.order_by(peewee.SQL("count(0) desc")))

if user_id:
query = query.where(Event.user == user_id)
Expand Down Expand Up @@ -1077,6 +1083,7 @@ class Dashboard(ChangeTrackingMixin, ModelTimestampsMixin, BaseVersionedModel, B
layout = peewee.TextField()
dashboard_filters_enabled = peewee.BooleanField(default=False)
is_archived = peewee.BooleanField(default=False, index=True)
is_draft = peewee.BooleanField(default=False, index=True)

class Meta:
db_table = 'dashboards'
Expand Down Expand Up @@ -1129,24 +1136,29 @@ def to_dict(self, with_widgets=False, user=None):
'dashboard_filters_enabled': self.dashboard_filters_enabled,
'widgets': widgets_layout,
'is_archived': self.is_archived,
'is_draft': self.is_draft,
'updated_at': self.updated_at,
'created_at': self.created_at,
'version': self.version
}

@classmethod
def all(cls, org, groups, user_id):
query = cls.select().\
join(Widget, peewee.JOIN_LEFT_OUTER, on=(Dashboard.id == Widget.dashboard)). \
join(Visualization, peewee.JOIN_LEFT_OUTER, on=(Widget.visualization == Visualization.id)). \
join(Query, peewee.JOIN_LEFT_OUTER, on=(Visualization.query == Query.id)). \
join(DataSourceGroup, peewee.JOIN_LEFT_OUTER, on=(Query.data_source == DataSourceGroup.data_source)). \
where(Dashboard.is_archived == False). \
where((DataSourceGroup.group << groups) |
(Dashboard.user == user_id) |
(~(Widget.dashboard >> None) & (Widget.visualization >> None))). \
where(Dashboard.org == org). \
group_by(Dashboard.id)
query = (cls.select()
.join(Widget, peewee.JOIN_LEFT_OUTER,
on=(Dashboard.id == Widget.dashboard))
.join(Visualization, peewee.JOIN_LEFT_OUTER,
on=(Widget.visualization == Visualization.id))
.join(Query, peewee.JOIN_LEFT_OUTER,
on=(Visualization.query == Query.id))
.join(DataSourceGroup, peewee.JOIN_LEFT_OUTER,
on=(Query.data_source == DataSourceGroup.data_source))
.where(Dashboard.is_archived == False)
.where((DataSourceGroup.group << groups & (Dashboard.is_draft != True)) |
(Dashboard.user == user_id) |
(~(Widget.dashboard >> None) & (Widget.visualization >> None)))
.where(Dashboard.org == org)
.group_by(Dashboard.id))

return query

Expand All @@ -1162,6 +1174,7 @@ def recent(cls, org, groups, user_id, for_user=False, limit=20):
where(~(Event.object_id >> None)). \
where(Event.object_type == 'dashboard'). \
where(Dashboard.is_archived == False). \
where(Dashboard.is_draft == False). \
where(Dashboard.org == org). \
where((DataSourceGroup.group << groups) |
(Dashboard.user == user_id) |
Expand Down
2 changes: 2 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def __call__(self):
query='SELECT 1',
user=user_factory.create,
is_archived=False,
is_draft=False,
schedule=None,
data_source=data_source_factory.create,
org=1)
Expand All @@ -79,6 +80,7 @@ def __call__(self):
query='SELECT {{param1}}',
user=user_factory.create,
is_archived=False,
is_draft=False,
schedule=None,
data_source=data_source_factory.create,
org=1)
Expand Down
2 changes: 1 addition & 1 deletion tests/handlers/test_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ def test_works_for_non_owner_with_permission(self):
self.assertEqual(rv.json['last_modified_by']['id'], user.id)



class TestQueryListResourcePost(BaseTestCase):
def test_create_query(self):
query_data = {
Expand All @@ -110,6 +109,7 @@ def test_create_query(self):

query = models.Query.get_by_id(rv.json['id'])
self.assertEquals(len(list(query.visualizations)), 1)
self.assertTrue(query.is_draft)


class QueryRefreshTest(BaseTestCase):
Expand Down
26 changes: 26 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,22 @@ def test_global_recent(self):
self.assertIn(q1, recent)
self.assertNotIn(q2, recent)

def test_recent_excludes_drafts(self):
q1 = self.factory.create_query()
q2 = self.factory.create_query(is_draft=True)

models.Event.create(org=self.factory.org, user=self.factory.user,
action="edit", object_type="query",
object_id=q1.id)
models.Event.create(org=self.factory.org, user=self.factory.user,
action="edit", object_type="query",
object_id=q2.id)

recent = models.Query.recent([self.factory.default_group])

self.assertIn(q1, recent)
self.assertNotIn(q2, recent)

def test_recent_for_user(self):
q1 = self.factory.create_query()
q2 = self.factory.create_query()
Expand Down Expand Up @@ -657,6 +673,16 @@ def test_returns_recent_dashboards_basic(self):
self.assertNotIn(self.w2.dashboard, models.Dashboard.recent(self.u1.org, self.u1.groups, None))
self.assertNotIn(self.w1.dashboard, models.Dashboard.recent(self.u1.org, self.u2.groups, None))

def test_recent_excludes_drafts(self):
models.Event.create(org=self.factory.org, user=self.u1, action="view",
object_type="dashboard", object_id=self.w1.dashboard.id)
models.Event.create(org=self.factory.org, user=self.u1, action="view",
object_type="dashboard", object_id=self.w2.dashboard.id)

self.w2.dashboard.update_instance(is_draft=True)
self.assertIn(self.w1.dashboard, models.Dashboard.recent(self.u1.org, self.u1.groups, None))
self.assertNotIn(self.w2.dashboard, models.Dashboard.recent(self.u1.org, self.u1.groups, None))

def test_returns_recent_dashboards_created_by_user(self):
d1 = self.factory.create_dashboard(user=self.u1)
models.Event.create(org=self.factory.org, user=self.u1, action="view",
Expand Down

0 comments on commit 971f961

Please sign in to comment.