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 diff --git a/rd_ui/app/index.html b/rd_ui/app/index.html index 67cac81ddb..8f5cdcbeba 100644 --- a/rd_ui/app/index.html +++ b/rd_ui/app/index.html @@ -109,11 +109,11 @@ - + + - diff --git a/rd_ui/app/scripts/controllers/dashboard.js b/rd_ui/app/scripts/controllers/dashboard.js index de388bdc02..b1daba4dda 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, 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', '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/filters.js b/rd_ui/app/scripts/filters.js index 0ecb0e4e6f..4e3665e038 100644 --- a/rd_ui/app/scripts/filters.js +++ b/rd_ui/app/scripts/filters.js @@ -1,61 +1,67 @@ var durationHumanize = function (duration) { - var humanized = ""; - if (duration == undefined) { - humanized = "-"; - } else if (duration < 60) { - humanized = Math.round(duration) + "s"; - } else if (duration > 3600*24) { - var days = Math.round(parseFloat(duration) / 60.0 / 60.0 / 24.0); - humanized = days + "days"; - } else if (duration >= 3600) { - var hours = Math.round(parseFloat(duration) / 60.0 / 60.0); - humanized = hours + "h"; - } else { - var minutes = Math.round(parseFloat(duration) / 60.0); - humanized = minutes + "m"; - } - return humanized; + var humanized = ""; + if (duration == undefined) { + humanized = "-"; + } else if (duration < 60) { + humanized = Math.round(duration) + "s"; + } else if (duration > 3600 * 24) { + var days = Math.round(parseFloat(duration) / 60.0 / 60.0 / 24.0); + humanized = days + "days"; + } else if (duration >= 3600) { + var hours = Math.round(parseFloat(duration) / 60.0 / 60.0); + humanized = hours + "h"; + } else { + var minutes = Math.round(parseFloat(duration) / 60.0); + humanized = minutes + "m"; + } + return humanized; } angular.module('redash.filters', []). - filter('durationHumanize', function () { - return durationHumanize; - }) + filter('durationHumanize', function () { + return durationHumanize; + }) - .filter('refreshRateHumanize', function () { - return function (ttl) { - if (ttl==-1) { - return "Never"; - } else { - return "Every " + durationHumanize(ttl); - } - } - }) + .filter('refreshRateHumanize', function () { + return function (ttl) { + if (ttl == -1) { + return "Never"; + } else { + return "Every " + durationHumanize(ttl); + } + } + }) - .filter('toHuman', function() { - return function(text) { - return text.replace(/_/g, ' ').replace(/(?:^|\s)\S/g, function (a) { - return a.toUpperCase(); - }); - } - }) + .filter('toHuman', function () { + return function (text) { + return text.replace(/_/g, ' ').replace(/(?:^|\s)\S/g, function (a) { + return a.toUpperCase(); + }); + } + }) + + .filter('colWidth', function () { + return function (widgetWidth) { + if (widgetWidth == 1) { + return 6; + } + return 12; + } + }) - .filter('colWidth', function () { - return function (widgetWidth) { - if (widgetWidth == 1) { - return 6; - } - return 12; - } - }) - - .filter('capitalize', function () { - return function (text) { - if (text) { - return text[0].toUpperCase() + text.slice(1).toLowerCase(); - } else { - return null; - } - - } - }); \ No newline at end of file + .filter('capitalize', function () { + return function (text) { + if (text) { + return _.str.capitalize(text); + } else { + return null; + } + + } + }) + + .filter('markdown', ['$sce', function($sce) { + return function(text) { + return $sce.trustAsHtml(marked(text)); + } + }]); \ No newline at end of file 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..b50b6559f3 100644 --- a/rd_ui/app/views/dashboard.html +++ b/rd_ui/app/views/dashboard.html @@ -21,7 +21,7 @@
-
++ +
-+
+ + +