From 4ffd21be099c0d919fef806c920fa9ca0a7e4835 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Tue, 29 Apr 2014 10:35:47 +0300 Subject: [PATCH 1/5] Migration to add text column to widgets and make visualization_id nullable. --- migrations/add_text_to_widgets.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 migrations/add_text_to_widgets.py diff --git a/migrations/add_text_to_widgets.py b/migrations/add_text_to_widgets.py new file mode 100644 index 0000000000..50a40fd914 --- /dev/null +++ b/migrations/add_text_to_widgets.py @@ -0,0 +1,13 @@ +from playhouse.migrate import Migrator +from redash import db +from redash import models + + +if __name__ == '__main__': + db.connect_db() + migrator = Migrator(db.database) + with db.database.transaction(): + migrator.add_column(models.Widget, models.Widget.text, 'text') + migrator.set_nullable(models.Widget, models.Widget.visualization, True) + + db.close_db(None) \ No newline at end of file From 850ac9f4c881ae08a37e97ea5e7eff80e27e97bc Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Tue, 29 Apr 2014 10:36:00 +0300 Subject: [PATCH 2/5] Add underscore.string lib. --- rd_ui/app/index.html | 2 +- rd_ui/bower.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rd_ui/app/index.html b/rd_ui/app/index.html index 67cac81ddb..e1c4f48044 100644 --- a/rd_ui/app/index.html +++ b/rd_ui/app/index.html @@ -109,7 +109,7 @@ - + diff --git a/rd_ui/bower.json b/rd_ui/bower.json index 5f9c86d88b..e95631a331 100644 --- a/rd_ui/bower.json +++ b/rd_ui/bower.json @@ -20,7 +20,8 @@ "cornelius": "https://github.com/restorando/cornelius.git", "gridster": "0.2.0", "mousetrap": "~1.4.6", - "angular-ui-select2": "~0.0.5" + "angular-ui-select2": "~0.0.5", + "underscore.string": "~2.3.3" }, "devDependencies": { "angular-mocks": "~1.0.7", From 289d38b2a6f4e3b1cea028c034f3c43492110823 Mon Sep 17 00:00:00 2001 From: Arik Fraimovich Date: Tue, 29 Apr 2014 10:36:56 +0300 Subject: [PATCH 3/5] Add textbox widget support. --- rd_ui/app/scripts/controllers/dashboard.js | 41 +++++++------- .../directives/dashboard_directives.js | 36 +++++++++---- rd_ui/app/scripts/services/resources.js | 7 +++ rd_ui/app/views/dashboard.html | 11 +++- rd_ui/app/views/new_widget_form.html | 54 ++++++++++++------- redash/models.py | 23 ++++---- tests/test_controllers.py | 17 ++++++ 7 files changed, 130 insertions(+), 59 deletions(-) diff --git a/rd_ui/app/scripts/controllers/dashboard.js b/rd_ui/app/scripts/controllers/dashboard.js index de388bdc02..878b5f3b49 100644 --- a/rd_ui/app/scripts/controllers/dashboard.js +++ b/rd_ui/app/scripts/controllers/dashboard.js @@ -1,13 +1,16 @@ (function() { - var DashboardCtrl = function($scope, Events, $routeParams, $http, $timeout, Dashboard) { + var DashboardCtrl = function($scope, Events, Widget, $routeParams, $http, $timeout, Dashboard) { Events.record(currentUser, "view", "dashboard", dashboard.id); $scope.refreshEnabled = false; $scope.refreshRate = 60; - $scope.dashboard = Dashboard.get({ - slug: $routeParams.dashboardSlug - }, function(dashboard) { + $scope.dashboard = Dashboard.get({ slug: $routeParams.dashboardSlug }, function (dashboard) { $scope.$parent.pageTitle = dashboard.name; + $scope.dashboard.widgets = _.map($scope.dashboard.widgets, function (row) { + return _.map(row, function (widget) { + return new Widget(widget); + }); + }); }); var autoRefresh = function() { @@ -51,39 +54,41 @@ }; }; - var WidgetCtrl = function($scope, Events, $http, $location, Query) { + var WidgetCtrl = function($scope, Events, Widget, $http, $location, Query) { $scope.deleteWidget = function() { - if (!confirm('Are you sure you want to remove "' + $scope.widget.visualization.name + '" from the dashboard?')) { + if (!confirm('Are you sure you want to remove "' + $scope.widget.getName() + '" from the dashboard?')) { return; } Events.record(currentUser, "delete", "widget", $scope.widget.id); - $http.delete('/api/widgets/' + $scope.widget.id).success(function() { + $scope.widget.$delete(function() { $scope.dashboard.widgets = _.map($scope.dashboard.widgets, function(row) { return _.filter(row, function(widget) { - return widget.id != $scope.widget.id; + return widget.id != undefined; }) }); }); }; - // TODO: fire event for query view for each query Events.record(currentUser, "view", "widget", $scope.widget.id); - Events.record(currentUser, "view", "query", $scope.widget.visualization.query.id); - Events.record(currentUser, "view", "visualization", $scope.widget.visualization.id); - $scope.query = new Query($scope.widget.visualization.query); - $scope.queryResult = $scope.query.getQueryResult(); + if ($scope.widget.visualization) { + Events.record(currentUser, "view", "query", $scope.widget.visualization.query.id); + Events.record(currentUser, "view", "visualization", $scope.widget.visualization.id); - $scope.updateTime = (new Date($scope.queryResult.getUpdatedAt())).toISOString(); - $scope.nextUpdateTime = moment(new Date(($scope.query.updated_at + $scope.query.ttl + $scope.query.runtime + 300) * 1000)).fromNow(); + $scope.query = new Query($scope.widget.visualization.query); + $scope.queryResult = $scope.query.getQueryResult(); + $scope.nextUpdateTime = moment(new Date(($scope.query.updated_at + $scope.query.ttl + $scope.query.runtime + 300) * 1000)).fromNow(); - $scope.updateTime = ''; + $scope.type = 'visualization'; + } else { + $scope.type = 'textbox'; + } }; angular.module('redash.controllers') - .controller('DashboardCtrl', ['$scope', 'Events', '$routeParams', '$http', '$timeout', 'Dashboard', DashboardCtrl]) - .controller('WidgetCtrl', ['$scope', 'Events', '$http', '$location', 'Query', WidgetCtrl]) + .controller('DashboardCtrl', ['$scope', 'Events', 'Widget', '$routeParams', '$http', '$timeout', 'Dashboard', DashboardCtrl]) + .controller('WidgetCtrl', ['$scope', 'Events', 'Widget', '$http', '$location', 'Query', WidgetCtrl]) })(); \ No newline at end of file diff --git a/rd_ui/app/scripts/directives/dashboard_directives.js b/rd_ui/app/scripts/directives/dashboard_directives.js index c1bc2dde3e..3689070745 100644 --- a/rd_ui/app/scripts/directives/dashboard_directives.js +++ b/rd_ui/app/scripts/directives/dashboard_directives.js @@ -46,7 +46,7 @@ row: rowIndex + 1, ySize: 1, xSize: widget.width, - name: widget.visualization.query.name + name: widget.getName()//visualization.query.name }); }); }); @@ -125,25 +125,37 @@ value: 2 }]; + $scope.type = 'visualization'; + + $scope.isVisualization = function () { + return $scope.type == 'visualization'; + }; + + $scope.isTextBox = function () { + return $scope.type == 'textbox'; + }; + + $scope.setType = function (type) { + $scope.type = type; + }; + var reset = function() { $scope.saveInProgress = false; $scope.widgetSize = 1; $scope.queryId = null; $scope.selectedVis = null; $scope.query = null; - - } + $scope.text = ""; + }; reset(); - $scope.loadVisualizations = function() { + $scope.loadVisualizations = function () { if (!$scope.queryId) { return; } - Query.get({ - id: $scope.queryId - }, function(query) { + Query.get({ id: $scope.queryId }, function(query) { if (query) { $scope.query = query; if (query.visualizations.length) { @@ -157,19 +169,21 @@ $scope.saveInProgress = true; var widget = new Widget({ - 'visualization_id': $scope.selectedVis.id, + 'visualization_id': $scope.selectedVis && $scope.selectedVis.id, 'dashboard_id': $scope.dashboard.id, 'options': {}, - 'width': $scope.widgetSize + 'width': $scope.widgetSize, + 'text': $scope.text }); widget.$save().then(function(response) { // update dashboard layout $scope.dashboard.layout = response['layout']; + var newWidget = new Widget(response['widget']); if (response['new_row']) { - $scope.dashboard.widgets.push([response['widget']]); + $scope.dashboard.widgets.push([newWidget]); } else { - $scope.dashboard.widgets[$scope.dashboard.widgets.length - 1].push(response['widget']); + $scope.dashboard.widgets[$scope.dashboard.widgets.length - 1].push(newWidget); } // close the dialog diff --git a/rd_ui/app/scripts/services/resources.js b/rd_ui/app/scripts/services/resources.js index 621de4f583..429f9c61af 100644 --- a/rd_ui/app/scripts/services/resources.js +++ b/rd_ui/app/scripts/services/resources.js @@ -363,6 +363,13 @@ var Widget = function ($resource) { var WidgetResource = $resource('/api/widgets/:id', {id: '@id'}); + WidgetResource.prototype.getName = function () { + if (this.visualization) { + return this.visualization.query.name + ' (' + this.visualization.name + ')'; + } + return _.str.truncate(this.text, 20); + }; + return WidgetResource; } diff --git a/rd_ui/app/views/dashboard.html b/rd_ui/app/views/dashboard.html index d881680ac6..624187cbe3 100644 --- a/rd_ui/app/views/dashboard.html +++ b/rd_ui/app/views/dashboard.html @@ -21,7 +21,7 @@

-
+

@@ -43,7 +43,16 @@

+

+
+ +
+
+ {{widget.text}} + + +
diff --git a/rd_ui/app/views/new_widget_form.html b/rd_ui/app/views/new_widget_form.html index 10b41b43c6..395babf3dd 100644 --- a/rd_ui/app/views/new_widget_form.html +++ b/rd_ui/app/views/new_widget_form.html @@ -6,32 +6,46 @@