diff --git a/rd_ui/app/scripts/controllers/controllers.js b/rd_ui/app/scripts/controllers/controllers.js index 18542155a6..ec19c1d96d 100644 --- a/rd_ui/app/scripts/controllers/controllers.js +++ b/rd_ui/app/scripts/controllers/controllers.js @@ -1,216 +1,217 @@ -(function () { - var QueriesCtrl = function ($scope, $http, $location, $filter, Query) { - $scope.$parent.pageTitle = "All Queries"; - $scope.gridConfig = { - isPaginationEnabled: true, - itemsByPage: 50, - maxSize: 8, - isGlobalSearchActivated: true - } +(function() { + var QueriesCtrl = function(growl, $scope, $http, $location, $filter, Query) { + $scope.$parent.pageTitle = "All Queries"; + $scope.gridConfig = { + isPaginationEnabled: true, + itemsByPage: 50, + maxSize: 8, + isGlobalSearchActivated: true + } - $scope.allQueries = []; - $scope.queries = []; + $scope.allQueries = []; + $scope.queries = []; - var dateFormatter = function (value) { - if (!value) return "-"; - return value.format("DD/MM/YY HH:mm"); - } + var dateFormatter = function(value) { + if (!value) return "-"; + return value.format("DD/MM/YY HH:mm"); + } - var filterQueries = function () { - $scope.queries = _.filter($scope.allQueries, function (query) { - if (!$scope.selectedTab) { - return false; + var filterQueries = function() { + $scope.queries = _.filter($scope.allQueries, function(query) { + if (!$scope.selectedTab) { + return false; + } + + if ($scope.selectedTab.key == 'my') { + return query.user.id == currentUser.id && query.name != 'New Query'; + } else if ($scope.selectedTab.key == 'drafts') { + return query.user.id == currentUser.id && query.name == 'New Query'; + } + + return query.name != 'New Query'; + }); } - if ($scope.selectedTab.key == 'my') { - return query.user.id == currentUser.id && query.name != 'New Query'; - } else if ($scope.selectedTab.key == 'drafts') { - return query.user.id == currentUser.id && query.name == 'New Query'; + var deleteQueryfromQueries = function(query) { + query.queryWidget(function(data) { + $scope.queryUsed = data; + if (data.widget == null) { + if (confirm('Are you sure you want to delete "' + query.name + '" query?')) { + query.$delete(function() { + location.reload(); + }); + } + } else { + var options = { + errorQueryUsed: 'This query is used in a dashboard' + }; + growl.addErrorMessage(options.errorQueryUsed) + } + }); } - return query.name != 'New Query'; - }); - } + Query.query(function(queries) { + $scope.allQueries = _.map(queries, function(query) { - var deleteQueryfromQueries = function (query) { - if (confirm('Are you sure you want to delete "' + query.name + '" query?')) { - query.$delete(function () { - location.reload(); - }); - } - } - Query.query(function (queries) { - $scope.allQueries = _.map(queries, function (query) { + query.deleteQuery = deleteQueryfromQueries; - if (query.queryWidget().widget != null) { - query.isUsed = true; - } else { - query.isUsed = false; - query.deleteQuery = deleteQueryfromQueries; - } - - - query.created_at = moment(query.created_at); - query.last_retrieved_at = moment(query.last_retrieved_at); - return query; - }); - - filterQueries(); - }); - - $scope.gridColumns = [ - { - "label": "Name", - "map": "name", - "cellTemplateUrl": "/views/queries_query_name_cell.html" - }, - { - 'label': 'Created By', - 'map': 'user.name' - }, - { - 'label': 'Created At', - 'map': 'created_at', - 'formatFunction': dateFormatter - }, - { - 'label': 'Runtime (avg)', - 'map': 'avg_runtime', - 'formatFunction': function (value) { - return $filter('durationHumanize')(value); - } - }, - { - 'label': 'Runtime (min)', - 'map': 'min_runtime', - 'formatFunction': function (value) { - return $filter('durationHumanize')(value); - } - }, - { - 'label': 'Runtime (max)', - 'map': 'max_runtime', - 'formatFunction': function (value) { - return $filter('durationHumanize')(value); - } - }, - { - 'label': 'Last Executed At', - 'map': 'last_retrieved_at', - 'formatFunction': dateFormatter - }, - { - 'label': 'Times Executed', - 'map': 'times_retrieved' - }, - { - 'label': 'Update Schedule', - 'map': 'ttl', - 'formatFunction': function (value) { - return $filter('refreshRateHumanize')(value); + + query.created_at = moment(query.created_at); + query.last_retrieved_at = moment(query.last_retrieved_at); + return query; + }); + + filterQueries(); + }); + + $scope.gridColumns = [{ + "label": "Name", + "map": "name", + "cellTemplateUrl": "/views/queries_query_name_cell.html" + }, { + 'label': 'Created By', + 'map': 'user.name' + }, { + 'label': 'Created At', + 'map': 'created_at', + 'formatFunction': dateFormatter + }, { + 'label': 'Runtime (avg)', + 'map': 'avg_runtime', + 'formatFunction': function(value) { + return $filter('durationHumanize')(value); + } + }, { + 'label': 'Runtime (min)', + 'map': 'min_runtime', + 'formatFunction': function(value) { + return $filter('durationHumanize')(value); + } + }, { + 'label': 'Runtime (max)', + 'map': 'max_runtime', + 'formatFunction': function(value) { + return $filter('durationHumanize')(value); + } + }, { + 'label': 'Last Executed At', + 'map': 'last_retrieved_at', + 'formatFunction': dateFormatter + }, { + 'label': 'Times Executed', + 'map': 'times_retrieved' + }, { + 'label': 'Update Schedule', + 'map': 'ttl', + 'formatFunction': function(value) { + return $filter('refreshRateHumanize')(value); + } + }]; + + if ($.inArray('edit_query', currentUser.permissions > -1)) { + var deleteColumn = { + "label": "Delete", + "map": "delete", + "cellTemplateUrl": "/views/queries_delete_query_cell.html" + }; + + $scope.gridColumns.push(deleteColumn) } - } - ]; - if ($.inArray('edit_query', currentUser.permissions > -1)) { - var deleteColumn = { - "label": "Delete", - "map": "delete", - "cellTemplateUrl": "/views/queries_delete_query_cell.html" - }; - $scope.gridColumns.push(deleteColumn) - } - - - $scope.tabs = [ - {"name": "My Queries", "key": "my"}, - {"key": "all", "name": "All Queries"}, - {"key": "drafts", "name": "Drafts"} - ]; - - $scope.$watch('selectedTab', function (tab) { - if (tab) { - $scope.$parent.pageTitle = tab.name; - } - - filterQueries(); - }); - - - } - - var MainCtrl = function ($scope, Dashboard, notifications) { - if (featureFlags.clientSideMetrics) { - $scope.$on('$locationChangeSuccess', function(event, newLocation, oldLocation) { - // This will be called once per actual page load. - Bucky.sendPagePerformance(); - }); - } + $scope.tabs = [{ + "name": "My Queries", + "key": "my" + }, { + "key": "all", + "name": "All Queries" + }, { + "key": "drafts", + "name": "Drafts" + }]; + + $scope.$watch('selectedTab', function(tab) { + if (tab) { + $scope.$parent.pageTitle = tab.name; + } - $scope.queries = []; - $scope.reloadqueries = function () { - Query.query(function (queries) { - $scope.queries = _.sortBy(queries, "name"); - $scope.allQueries = _.groupBy($scope.queries, function (q) { - parts = q.name.split(":"); - if (parts.length == 1) { - return "Other"; - } - return parts[0]; + filterQueries(); }); - $scope.otherQueries = $scope.allQueries['Other'] || []; - - }); - } - $scope.dashboards = []; - $scope.reloadDashboards = function () { - Dashboard.query(function (dashboards) { - $scope.dashboards = _.sortBy(dashboards, "name"); - $scope.allDashboards = _.groupBy($scope.dashboards, function (d) { - parts = d.name.split(":"); - if (parts.length == 1) { - return "Other"; - } - return parts[0]; - }); - $scope.otherDashboards = $scope.allDashboards['Other'] || []; - $scope.groupedDashboards = _.omit($scope.allDashboards, 'Other'); - }); } - $scope.reloadDashboards(); + var MainCtrl = function($scope, Dashboard, notifications) { + if (featureFlags.clientSideMetrics) { + $scope.$on('$locationChangeSuccess', function(event, newLocation, oldLocation) { + // This will be called once per actual page load. + Bucky.sendPagePerformance(); + }); + } - $scope.currentUser = currentUser; - $scope.newDashboard = { - 'name': null, - 'layout': null - } + $scope.queries = []; + $scope.reloadqueries = function() { + Query.query(function(queries) { + $scope.queries = _.sortBy(queries, "name"); + $scope.allQueries = _.groupBy($scope.queries, function(q) { + parts = q.name.split(":"); + if (parts.length == 1) { + return "Other"; + } + return parts[0]; + }); + $scope.otherQueries = $scope.allQueries['Other'] || []; + + }); + } + + + $scope.dashboards = []; + $scope.reloadDashboards = function() { + Dashboard.query(function(dashboards) { + $scope.dashboards = _.sortBy(dashboards, "name"); + $scope.allDashboards = _.groupBy($scope.dashboards, function(d) { + parts = d.name.split(":"); + if (parts.length == 1) { + return "Other"; + } + return parts[0]; + }); + $scope.otherDashboards = $scope.allDashboards['Other'] || []; + $scope.groupedDashboards = _.omit($scope.allDashboards, 'Other'); + }); + } - $(window).click(function () { - notifications.getPermissions(); - }); - } + $scope.reloadDashboards(); - var IndexCtrl = function ($scope, Events, Dashboard) { - Events.record(currentUser, "view", "page", "homepage"); - $scope.$parent.pageTitle = "Home"; + $scope.currentUser = currentUser; + $scope.newDashboard = { + 'name': null, + 'layout': null + } - $scope.archiveDashboard = function (dashboard) { - if (confirm('Are you sure you want to delete "' + dashboard.name + '" dashboard?')) { - Events.record(currentUser, "archive", "dashboard", dashboard.id); - dashboard.$delete(function () { - $scope.$parent.reloadDashboards(); + $(window).click(function() { + notifications.getPermissions(); }); - } } - } - angular.module('redash.controllers', []) - .controller('QueriesCtrl', ['$scope', '$http', '$location', '$filter', 'Query', QueriesCtrl]) - .controller('IndexCtrl', ['$scope', 'Events', 'Dashboard', IndexCtrl]) - .controller('MainCtrl', ['$scope', 'Dashboard', 'notifications', MainCtrl]); -})(); + var IndexCtrl = function($scope, Events, Dashboard) { + Events.record(currentUser, "view", "page", "homepage"); + $scope.$parent.pageTitle = "Home"; + + $scope.archiveDashboard = function(dashboard) { + if (confirm('Are you sure you want to delete "' + dashboard.name + '" dashboard?')) { + Events.record(currentUser, "archive", "dashboard", dashboard.id); + dashboard.$delete(function() { + $scope.$parent.reloadDashboards(); + }); + } + } + } + + angular.module('redash.controllers', []) + .controller('QueriesCtrl', ['growl', '$scope', '$http', '$location', '$filter', 'Query', QueriesCtrl]) + .controller('IndexCtrl', ['$scope', 'Events', 'Dashboard', IndexCtrl]) + .controller('MainCtrl', ['$scope', 'Dashboard', 'notifications', MainCtrl]); +})(); \ No newline at end of file diff --git a/rd_ui/app/scripts/controllers/query_view.js b/rd_ui/app/scripts/controllers/query_view.js index c567b4d00f..b564d32dea 100644 --- a/rd_ui/app/scripts/controllers/query_view.js +++ b/rd_ui/app/scripts/controllers/query_view.js @@ -1,187 +1,203 @@ (function() { - 'use strict'; - - function QueryViewCtrl($scope, Events, $route, $timeout, $location, notifications, growl, Query, DataSource, Widget, Dashboard) { - var DEFAULT_TAB = 'table'; - - $scope.query = $route.current.locals.query; - Events.record(currentUser, 'view', 'query', $scope.query.id); - $scope.queryResult = $scope.query.getQueryResult(); - $scope.queryExecuting = false; - $scope.queryUsed = $scope.query.queryWidget(); - - $scope.isQueryOwner = currentUser.id === $scope.query.user.id; - $scope.canViewSource = currentUser.hasPermission('view_source'); - - $scope.dataSources = DataSource.get(function(dataSources) { - $scope.query.data_source_id = $scope.query.data_source_id || dataSources[0].id; - }); - - $scope.lockButton = function(lock) { - $scope.queryExecuting = lock; - }; - - $scope.saveQuery = function(options, data) { - if (data) { - data.id = $scope.query.id; - } else { - data = $scope.query; - } - - options = _.extend({}, { - successMessage: 'Query saved', - errorMessage: 'Query could not be saved' - }, options); - - delete $scope.query.latest_query_data; - delete $scope.query.queryResult; - - return Query.save(data, function() { - growl.addSuccessMessage(options.successMessage); - }, function(httpResponse) { - growl.addErrorMessage(options.errorMessage); - }).$promise; - } - - $scope.saveDescription = function() { - Events.record(currentUser, 'edit_description', 'query', $scope.query.id); - $scope.saveQuery(undefined, {'description': $scope.query.description}); - }; - - $scope.saveName = function() { - Events.record(currentUser, 'edit_name', 'query', $scope.query.id); - $scope.saveQuery(undefined, {'name': $scope.query.name}); - }; - - $scope.executeQuery = function() { - if ($scope.query.is_archived == false) { - $scope.queryResult = $scope.query.getQueryResult(0); - $scope.lockButton(true); - $scope.cancelling = false; - Events.record(currentUser, 'execute', 'query', $scope.query.id); - } - }; - - - $scope.goToBottom = function () { - window.scrollTo(0,9999999999999999); - } - - $scope.cancelExecution = function() { - $scope.cancelling = true; - $scope.queryResult.cancelExecution(); - Events.record(currentUser, 'cancel_execute', 'query', $scope.query.id); - }; - - $scope.deleteQuery = function () { - - - var options = { - successMessage: 'Query deleted', - errorMessage: 'Query could not be deleted', - errorQueryUsed: 'This query is used in a dashboard' - }; - - if ($scope.queryUsed.widget == null) { - var dashboards = $scope.dashboards - Events.record(currentUser, "archive", "query", $scope.query.id); - return $scope.query.$delete; - } else { - growl.addErrorMessage(options.errorQueryUsed); - } - } - - $scope.redirect = function() { - return $location.path('/queries'); - } - - $scope.updateDataSource = function() { - Events.record(currentUser, 'update_data_source', 'query', $scope.query.id); - $scope.query.latest_query_data = null; - $scope.query.latest_query_data_id = null; - Query.save({ - 'id': $scope.query.id, - 'data_source_id': $scope.query.data_source_id, - 'latest_query_data_id': null - }); - - $scope.executeQuery(); - }; - - $scope.setVisualizationTab = function (visualization) { - $scope.selectedTab = visualization.id; - $location.hash(visualization.id); + 'use strict'; + + function QueryViewCtrl($scope, Events, $route, $timeout, $location, notifications, growl, Query, DataSource, Widget, Dashboard) { + var DEFAULT_TAB = 'table'; + + $scope.query = $route.current.locals.query; + Events.record(currentUser, 'view', 'query', $scope.query.id); + $scope.queryResult = $scope.query.getQueryResult(); + $scope.queryExecuting = false; + + $scope.isQueryOwner = currentUser.id === $scope.query.user.id; + $scope.canViewSource = currentUser.hasPermission('view_source'); + + $scope.dataSources = DataSource.get(function(dataSources) { + $scope.query.data_source_id = $scope.query.data_source_id || dataSources[0].id; + }); + + $scope.lockButton = function(lock) { + $scope.queryExecuting = lock; + }; + + $scope.saveQuery = function(options, data) { + if (data) { + data.id = $scope.query.id; + } else { + data = $scope.query; + } + + options = _.extend({}, { + successMessage: 'Query saved', + errorMessage: 'Query could not be saved' + }, options); + + delete $scope.query.latest_query_data; + delete $scope.query.queryResult; + + return Query.save(data, function() { + growl.addSuccessMessage(options.successMessage); + }, function(httpResponse) { + growl.addErrorMessage(options.errorMessage); + }).$promise; + } + + $scope.saveDescription = function() { + Events.record(currentUser, 'edit_description', 'query', $scope.query.id); + $scope.saveQuery(undefined, { + 'description': $scope.query.description + }); + }; + + $scope.saveName = function() { + Events.record(currentUser, 'edit_name', 'query', $scope.query.id); + $scope.saveQuery(undefined, { + 'name': $scope.query.name + }); + }; + + $scope.executeQuery = function() { + if ($scope.query.is_archived == false) { + $scope.queryResult = $scope.query.getQueryResult(0); + $scope.lockButton(true); + $scope.cancelling = false; + Events.record(currentUser, 'execute', 'query', $scope.query.id); + } + }; + + + $scope.goToBottom = function() { + window.scrollTo(0, 9999999999999999); + } + + $scope.cancelExecution = function() { + $scope.cancelling = true; + $scope.queryResult.cancelExecution(); + Events.record(currentUser, 'cancel_execute', 'query', $scope.query.id); + }; + + $scope.setUpDelete = function() { + $scope.queryUsed = $scope.query.queryWidget(); + } + + $scope.deleteQuery = function(data) { + if (data) { + data.id = $scope.query.id; + } else { + data = $scope.query; + } + + var options = { + successMessage: 'Query deleted', + errorMessage: 'Query could not be deleted', + errorQueryUsed: 'This query is used in a dashboard' + }; + if ($scope.queryUsed.widget != null) { + growl.addErrorMessage(options.errorQueryUsed) + } else { + Events.record(currentUser, "archive", "query", $scope.query.id); + Query.delete(data, function() { + growl.addSuccessMessage(options.successMessage); + $location.path('/queries'); + }, function(httpResponse) { + growl.addErrorMessage(options.errorMessage); + }).$promise; + + } + } + + + + $scope.redirect = function() { + return $location.path('/queries'); + } + + $scope.updateDataSource = function() { + Events.record(currentUser, 'update_data_source', 'query', $scope.query.id); + $scope.query.latest_query_data = null; + $scope.query.latest_query_data_id = null; + Query.save({ + 'id': $scope.query.id, + 'data_source_id': $scope.query.data_source_id, + 'latest_query_data_id': null + }); + + $scope.executeQuery(); + }; + + $scope.setVisualizationTab = function(visualization) { + $scope.selectedTab = visualization.id; + $location.hash(visualization.id); + }; + + $scope.$watch('query.name', function() { + $scope.$parent.pageTitle = $scope.query.name; + }); + + $scope.$watch('queryResult && queryResult.getError()', function(newError, oldError) { + if (newError == undefined) { + return; + } + + if (oldError == undefined && newError != undefined) { + $scope.lockButton(false); + } + }); + + $scope.$watch('queryResult && queryResult.getData()', function(data, oldData) { + if (!data) { + return; + } + + $scope.filters = $scope.queryResult.getFilters(); + + if ($scope.queryResult.getId() == null) { + $scope.dataUri = ""; + } else { + $scope.dataUri = + '/api/queries/' + $scope.query.id + '/results/' + + $scope.queryResult.getId() + '.csv'; + + $scope.dataFilename = + $scope.query.name.replace(" ", "_") + + moment($scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + + ".csv"; + } + }); + + $scope.$watch("queryResult && queryResult.getStatus()", function(status) { + if (!status) { + return; + } + + if (status == "done") { + if ($scope.query.id && + $scope.query.latest_query_data_id != $scope.queryResult.getId() && + $scope.query.query_hash == $scope.queryResult.query_result.query_hash) { + Query.save({ + 'id': $scope.query.id, + 'latest_query_data_id': $scope.queryResult.getId() + }) + } + $scope.query.latest_query_data_id = $scope.queryResult.getId(); + + notifications.showNotification("re:dash", $scope.query.name + " updated."); + + $scope.lockButton(false); + } + }); + + $scope.$watch(function() { + return $location.hash() + }, function(hash) { + if (hash == 'pivot') { + Events.record(currentUser, 'pivot', 'query', $scope.query && $scope.query.id); + } + + $scope.selectedTab = hash || DEFAULT_TAB; + }); }; - $scope.$watch('query.name', function() { - $scope.$parent.pageTitle = $scope.query.name; - }); - - $scope.$watch('queryResult && queryResult.getError()', function(newError, oldError) { - if (newError == undefined) { - return; - } - - if (oldError == undefined && newError != undefined) { - $scope.lockButton(false); - } - }); - - $scope.$watch('queryResult && queryResult.getData()', function(data, oldData) { - if (!data) { - return; - } - - $scope.filters = $scope.queryResult.getFilters(); - - if ($scope.queryResult.getId() == null) { - $scope.dataUri = ""; - } else { - $scope.dataUri = - '/api/queries/' + $scope.query.id + '/results/' + - $scope.queryResult.getId() + '.csv'; - - $scope.dataFilename = - $scope.query.name.replace(" ", "_") + - moment($scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + - ".csv"; - } - }); - - $scope.$watch("queryResult && queryResult.getStatus()", function(status) { - if (!status) { - return; - } - - if (status == "done") { - if ($scope.query.id && - $scope.query.latest_query_data_id != $scope.queryResult.getId() && - $scope.query.query_hash == $scope.queryResult.query_result.query_hash) { - Query.save({ - 'id': $scope.query.id, - 'latest_query_data_id': $scope.queryResult.getId() - }) - } - $scope.query.latest_query_data_id = $scope.queryResult.getId(); - - notifications.showNotification("re:dash", $scope.query.name + " updated."); - - $scope.lockButton(false); - } - }); - - $scope.$watch(function() { - return $location.hash() - }, function(hash) { - if (hash == 'pivot') { - Events.record(currentUser, 'pivot', 'query', $scope.query && $scope.query.id); - } - - $scope.selectedTab = hash || DEFAULT_TAB; - }); - }; - - angular.module('redash.controllers') - .controller('QueryViewCtrl', - ['$scope', 'Events', '$route', '$timeout', '$location', 'notifications', 'growl', 'Query', 'DataSource', 'Widget', 'Dashboard', QueryViewCtrl]); + angular.module('redash.controllers') + .controller('QueryViewCtrl', ['$scope', 'Events', '$route', '$timeout', '$location', 'notifications', 'growl', 'Query', 'DataSource', 'Widget', 'Dashboard', QueryViewCtrl]); })(); \ 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 979109da43..b655749f1c 100644 --- a/rd_ui/app/scripts/services/resources.js +++ b/rd_ui/app/scripts/services/resources.js @@ -1,474 +1,529 @@ -(function () { - var QueryResult = function ($resource, $timeout) { - var QueryResultResource = $resource('/api/query_results/:id', {id: '@id'}, {'post': {'method': 'POST'}}); - var Job = $resource('/api/jobs/:id', {id: '@id'}); - - var updateFunction = function (props) { - angular.extend(this, props); - if ('query_result' in props) { - this.status = "done"; - this.filters = undefined; - this.filterFreeze = undefined; - - _.each(this.query_result.data.rows, function (row) { - _.each(row, function (v, k) { - if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}/)) { - row[k] = moment(v); +(function() { + var QueryResult = function($resource, $timeout) { + var QueryResultResource = $resource('/api/query_results/:id', { + id: '@id' + }, { + 'post': { + 'method': 'POST' } - }); }); - } else if (this.job.status == 3) { - this.status = "processing"; - } else { - this.status = undefined; - } - } - - function QueryResult(props) { - this.job = {}; - this.query_result = {}; - this.status = "waiting"; - this.filters = undefined; - this.filterFreeze = undefined; + var Job = $resource('/api/jobs/:id', { + id: '@id' + }); - this.updatedAt = moment(); + var updateFunction = function(props) { + angular.extend(this, props); + if ('query_result' in props) { + this.status = "done"; + this.filters = undefined; + this.filterFreeze = undefined; + + _.each(this.query_result.data.rows, function(row) { + _.each(row, function(v, k) { + if (_.isString(v) && v.match(/^\d{4}-\d{2}-\d{2}/)) { + row[k] = moment(v); + } + }); + }); + } else if (this.job.status == 3) { + this.status = "processing"; + } else { + this.status = undefined; + } + } - if (props) { - updateFunction.apply(this, [props]); - } - } + function QueryResult(props) { + this.job = {}; + this.query_result = {}; + this.status = "waiting"; + this.filters = undefined; + this.filterFreeze = undefined; - var statuses = { - 1: "waiting", - 2: "processing", - 3: "done", - 4: "failed" - } + this.updatedAt = moment(); - QueryResult.prototype.update = updateFunction; + if (props) { + updateFunction.apply(this, [props]); + } + } - QueryResult.prototype.getId = function () { - var id = null; - if ('query_result' in this) { - id = this.query_result.id; - } - return id; - } + var statuses = { + 1: "waiting", + 2: "processing", + 3: "done", + 4: "failed" + } - QueryResult.prototype.cancelExecution = function () { - Job.delete({id: this.job.id}); - } + QueryResult.prototype.update = updateFunction; - QueryResult.prototype.getStatus = function () { - return this.status || statuses[this.job.status]; - } + QueryResult.prototype.getId = function() { + var id = null; + if ('query_result' in this) { + id = this.query_result.id; + } + return id; + } - QueryResult.prototype.getError = function () { - // TODO: move this logic to the server... - if (this.job.error == "None") { - return undefined; - } + QueryResult.prototype.cancelExecution = function() { + Job.delete({ + id: this.job.id + }); + } - return this.job.error; - } + QueryResult.prototype.getStatus = function() { + return this.status || statuses[this.job.status]; + } - QueryResult.prototype.getUpdatedAt = function () { - return this.query_result.retrieved_at || this.job.updated_at * 1000.0 || this.updatedAt; - } + QueryResult.prototype.getError = function() { + // TODO: move this logic to the server... + if (this.job.error == "None") { + return undefined; + } - QueryResult.prototype.getRuntime = function () { - return this.query_result.runtime; - } + return this.job.error; + } - QueryResult.prototype.getRawData = function () { - if (!this.query_result.data) { - return null; - } + QueryResult.prototype.getUpdatedAt = function() { + return this.query_result.retrieved_at || this.job.updated_at * 1000.0 || this.updatedAt; + } - var data = this.query_result.data.rows; + QueryResult.prototype.getRuntime = function() { + return this.query_result.runtime; + } - return data; - } + QueryResult.prototype.getRawData = function() { + if (!this.query_result.data) { + return null; + } - QueryResult.prototype.getData = function () { - if (!this.query_result.data) { - return null; - } + var data = this.query_result.data.rows; - var filterValues = function (filters) { - if (!filters) { - return null; + return data; } - return _.reduce(filters, function (str, filter) { - return str + filter.current; - }, "") - } - - var filters = this.getFilters(); - var filterFreeze = filterValues(filters); - - if (this.filterFreeze != filterFreeze) { - this.filterFreeze = filterFreeze; - - if (filters) { - this.filteredData = _.filter(this.query_result.data.rows, function (row) { - return _.reduce(filters, function (memo, filter) { - if (!_.isArray(filter.current)) { - filter.current = [filter.current]; - }; - - return (memo && _.some(filter.current, function(v) { - // We compare with either the value or the String representation of the value, - // because Select2 casts true/false to "true"/"false". - return v == row[filter.name] || String(row[filter.name]) == v - })); - }, true); - }); - } else { - this.filteredData = this.query_result.data.rows; - } - } + QueryResult.prototype.getData = function() { + if (!this.query_result.data) { + return null; + } - return this.filteredData; - } + var filterValues = function(filters) { + if (!filters) { + return null; + } - QueryResult.prototype.getChartData = function () { - var series = {}; - - _.each(this.getData(), function (row) { - var point = {}; - var seriesName = undefined; - var xValue = 0; - var yValues = {}; - - _.each(row, function (value, definition) { - var type = definition.split("::")[1]; - var name = definition.split("::")[0]; - - if (type == 'x') { - xValue = value; - point[type] = value; - } - if (type == 'y') { - if (value == null) { - value = 0; + return _.reduce(filters, function(str, filter) { + return str + filter.current; + }, "") } - yValues[name] = value; - point[type] = value; - } - if (type == 'series') { - seriesName = String(value); - } + var filters = this.getFilters(); + var filterFreeze = filterValues(filters); + + if (this.filterFreeze != filterFreeze) { + this.filterFreeze = filterFreeze; + + if (filters) { + this.filteredData = _.filter(this.query_result.data.rows, function(row) { + return _.reduce(filters, function(memo, filter) { + if (!_.isArray(filter.current)) { + filter.current = [filter.current]; + }; + + return (memo && _.some(filter.current, function(v) { + // We compare with either the value or the String representation of the value, + // because Select2 casts true/false to "true"/"false". + return v == row[filter.name] || String(row[filter.name]) == v + })); + }, true); + }); + } else { + this.filteredData = this.query_result.data.rows; + } + } - if (type == 'multi-filter') { - seriesName = String(value); - } - }); + return this.filteredData; + } - var addPointToSeries = function (seriesName, point) { - if (series[seriesName] == undefined) { - series[seriesName] = { - name: seriesName, - type: 'column', - data: [] + QueryResult.prototype.getChartData = function() { + var series = {}; + + _.each(this.getData(), function(row) { + var point = {}; + var seriesName = undefined; + var xValue = 0; + var yValues = {}; + + _.each(row, function(value, definition) { + var type = definition.split("::")[1]; + var name = definition.split("::")[0]; + + if (type == 'x') { + xValue = value; + point[type] = value; + } + if (type == 'y') { + if (value == null) { + value = 0; + } + yValues[name] = value; + point[type] = value; + } + + if (type == 'series') { + seriesName = String(value); + } + + if (type == 'multi-filter') { + seriesName = String(value); + } + }); + + var addPointToSeries = function(seriesName, point) { + if (series[seriesName] == undefined) { + series[seriesName] = { + name: seriesName, + type: 'column', + data: [] + } + } + + series[seriesName]['data'].push(point); + } + + if (seriesName === undefined) { + _.each(yValues, function(yValue, seriesName) { + addPointToSeries(seriesName, { + 'x': xValue, + 'y': yValue + }); + }); + } else { + addPointToSeries(seriesName, point); + } + }); + + _.each(series, function(series) { + series.data = _.sortBy(series.data, 'x'); + }); + + return _.values(series); + }; + + QueryResult.prototype.getColumns = function() { + if (this.columns == undefined && this.query_result.data) { + this.columns = this.query_result.data.columns; } - } - series[seriesName]['data'].push(point); + return this.columns; } - if (seriesName === undefined) { - _.each(yValues, function (yValue, seriesName) { - addPointToSeries(seriesName, {'x': xValue, 'y': yValue}); - }); - } else { - addPointToSeries(seriesName, point); - } - }); + QueryResult.prototype.getColumnNames = function() { + if (this.columnNames == undefined && this.query_result.data) { + this.columnNames = _.map(this.query_result.data.columns, function(v) { + return v.name; + }); + } - _.each(series, function (series) { - series.data = _.sortBy(series.data, 'x'); - }); + return this.columnNames; + } - return _.values(series); - }; + QueryResult.prototype.getColumnNameWithoutType = function(column) { + var parts = column.split('::'); + return parts[0]; + }; + + var charConversionMap = { + '__pct': /%/g, + '_': / /g, + '__qm': /\?/g, + '__brkt': /[\(\)\[\]]/g, + '__dash': /-/g, + '__amp': /&/g + }; + + QueryResult.prototype.getColumnCleanName = function(column) { + var name = this.getColumnNameWithoutType(column); + + if (name != '') { + _.each(charConversionMap, function(regex, replacement) { + name = name.replace(regex, replacement); + }); + } - QueryResult.prototype.getColumns = function () { - if (this.columns == undefined && this.query_result.data) { - this.columns = this.query_result.data.columns; - } - - return this.columns; - } + return name; + } - QueryResult.prototype.getColumnNames = function () { - if (this.columnNames == undefined && this.query_result.data) { - this.columnNames = _.map(this.query_result.data.columns, function (v) { - return v.name; - }); - } + QueryResult.prototype.getColumnFriendlyName = function(column) { + return this.getColumnNameWithoutType(column).replace(/(?:^|\s)\S/g, function(a) { + return a.toUpperCase(); + }); + } - return this.columnNames; - } + QueryResult.prototype.getColumnCleanNames = function() { + return _.map(this.getColumnNames(), function(col) { + return this.getColumnCleanName(col); + }, this); + } - QueryResult.prototype.getColumnNameWithoutType = function (column) { - var parts = column.split('::'); - return parts[0]; - }; + QueryResult.prototype.getColumnFriendlyNames = function() { + return _.map(this.getColumnNames(), function(col) { + return this.getColumnFriendlyName(col); + }, this); + } - var charConversionMap = { - '__pct': /%/g, - '_': / /g, - '__qm': /\?/g, - '__brkt': /[\(\)\[\]]/g, - '__dash': /-/g, - '__amp': /&/g - }; + QueryResult.prototype.getFilters = function() { + if (!this.filters) { + this.prepareFilters(); + } - QueryResult.prototype.getColumnCleanName = function (column) { - var name = this.getColumnNameWithoutType(column); + return this.filters; + }; + + QueryResult.prototype.prepareFilters = function() { + var filters = []; + var filterTypes = ['filter', 'multi-filter']; + _.each(this.getColumnNames(), function(col) { + var type = col.split('::')[1] + if (_.contains(filterTypes, type)) { + // filter found + var filter = { + name: col, + friendlyName: this.getColumnFriendlyName(col), + values: [], + multiple: (type == 'multi-filter') + } + filters.push(filter); + } + }, this); + + _.each(this.getRawData(), function(row) { + _.each(filters, function(filter) { + filter.values.push(row[filter.name]); + if (filter.values.length == 1) { + filter.current = row[filter.name]; + } + }) + }); + + _.each(filters, function(filter) { + filter.values = _.uniq(filter.values); + }); + + this.filters = filters; + } - if (name != '') { - _.each(charConversionMap, function(regex, replacement) { - name = name.replace(regex, replacement); - }); - } + var refreshStatus = function(queryResult, query, ttl) { + Job.get({ + 'id': queryResult.job.id + }, function(response) { + queryResult.update(response); + + if (queryResult.getStatus() == "processing" && queryResult.job.query_result_id && queryResult.job.query_result_id != "None") { + QueryResultResource.get({ + 'id': queryResult.job.query_result_id + }, function(response) { + queryResult.update(response); + }); + } else if (queryResult.getStatus() != "failed") { + $timeout(function() { + refreshStatus(queryResult, query, ttl); + }, 3000); + } + }) + } - return name; - } + QueryResult.getById = function(id) { + var queryResult = new QueryResult(); - QueryResult.prototype.getColumnFriendlyName = function (column) { - return this.getColumnNameWithoutType(column).replace(/(?:^|\s)\S/g, function (a) { - return a.toUpperCase(); - }); - } + QueryResultResource.get({ + 'id': id + }, function(response) { + queryResult.update(response); + }); - QueryResult.prototype.getColumnCleanNames = function () { - return _.map(this.getColumnNames(), function (col) { - return this.getColumnCleanName(col); - }, this); - } + return queryResult; + } - QueryResult.prototype.getColumnFriendlyNames = function () { - return _.map(this.getColumnNames(), function (col) { - return this.getColumnFriendlyName(col); - }, this); - } + QueryResult.get = function(data_source_id, query, ttl) { + var queryResult = new QueryResult(); - QueryResult.prototype.getFilters = function () { - if (!this.filters) { - this.prepareFilters(); - } + QueryResultResource.post({ + 'data_source_id': data_source_id, + 'query': query, + 'ttl': ttl + }, function(response) { + queryResult.update(response); - return this.filters; - }; + if ('job' in response) { + refreshStatus(queryResult, query, ttl); + } + }); - QueryResult.prototype.prepareFilters = function () { - var filters = []; - var filterTypes = ['filter', 'multi-filter']; - _.each(this.getColumnNames(), function (col) { - var type = col.split('::')[1] - if (_.contains(filterTypes, type)) { - // filter found - var filter = { - name: col, - friendlyName: this.getColumnFriendlyName(col), - values: [], - multiple: (type=='multi-filter') - } - filters.push(filter); + return queryResult; } - }, this); - - _.each(this.getRawData(), function (row) { - _.each(filters, function (filter) { - filter.values.push(row[filter.name]); - if (filter.values.length == 1) { - filter.current = row[filter.name]; - } - }) - }); - _.each(filters, function(filter) { - filter.values = _.uniq(filter.values); - }); - - this.filters = filters; - } - - var refreshStatus = function (queryResult, query, ttl) { - Job.get({'id': queryResult.job.id}, function (response) { - queryResult.update(response); - - if (queryResult.getStatus() == "processing" && queryResult.job.query_result_id && queryResult.job.query_result_id != "None") { - QueryResultResource.get({'id': queryResult.job.query_result_id}, function (response) { - queryResult.update(response); - }); - } else if (queryResult.getStatus() != "failed") { - $timeout(function () { - refreshStatus(queryResult, query, ttl); - }, 3000); - } - }) - } + return QueryResult; + }; - QueryResult.getById = function (id) { - var queryResult = new QueryResult(); - QueryResultResource.get({'id': id}, function (response) { - queryResult.update(response); - }); + var Query = function($resource, QueryResult, DataSource) { + var Query = $resource('/api/queries/:id', { + id: '@id' + }); + var queryWidget = $resource('/api/widget_check/:id'); + + Query.newQuery = function() { + return new Query({ + query: "", + name: "New Query", + ttl: -1, + user: currentUser + }); + }; + + Query.prototype.getSourceLink = function() { + return '/queries/' + this.id + '/source'; + }; + + Query.prototype.queryWidget = function(fn) { + if (this.id != null) { + return queryWidget.get({ + id: this.id + }, fn); + } + return null + }; - return queryResult; - } + Query.prototype.getQueryResult = function(ttl) { + if (ttl == undefined) { + ttl = this.ttl; + } - QueryResult.get = function (data_source_id, query, ttl) { - var queryResult = new QueryResult(); + var queryResult = null; - QueryResultResource.post({'data_source_id': data_source_id, 'query': query, 'ttl': ttl}, function (response) { - queryResult.update(response); + if (this.latest_query_data && ttl != 0) { - if ('job' in response) { - refreshStatus(queryResult, query, ttl); - } - }); + if (!this.queryResult) { + this.queryResult = new QueryResult({ + 'query_result': this.latest_query_data + }); - return queryResult; - } + } + queryResult = this.queryResult; + } else if (this.latest_query_data_id && ttl != 0) { + queryResult = QueryResult.getById(this.latest_query_data_id); + } else if (this.data_source_id) { + queryResult = QueryResult.get(this.data_source_id, this.query, ttl); + } - return QueryResult; - }; + return queryResult; + }; + return Query; + }; - var Query = function ($resource, QueryResult, DataSource) { - var Query = $resource('/api/queries/:id', {id: '@id'}); - var queryWidget = $resource('/api/widget_check/:id'); + var DataSource = function($resource) { + var DataSourceResource = $resource('/api/data_sources/:id', { + id: '@id' + }, { + 'get': { + 'method': 'GET', + 'cache': true, + 'isArray': true + } + }); - Query.newQuery = function () { - return new Query({ - query: "", - name: "New Query", - ttl: -1, - user: currentUser - }); - }; + return DataSourceResource; + } - Query.prototype.getSourceLink = function () { - return '/queries/' + this.id + '/source'; - }; - Query.prototype.queryWidget = function () { - if (this.id != null) { - return queryWidget.get({id:this.id}); - } - return null - }; + var Users = function($resource) { + var UserResource = $resource('/api/users', {}, { + 'get': { + 'method': 'GET', + 'cache': true, + 'isArray': true + } + }) + UserResource.prototype.getUsers = function() { + return UserResource.get(); + }; + return UserResource; + } - Query.prototype.getQueryResult = function (ttl) { - if (ttl == undefined) { - ttl = this.ttl; - } - - var queryResult = null; - - if (this.latest_query_data && ttl != 0) { - - if (!this.queryResult) { - this.queryResult = new QueryResult({'query_result': this.latest_query_data}); - - } - queryResult = this.queryResult; - } else if (this.latest_query_data_id && ttl != 0) { - queryResult = QueryResult.getById(this.latest_query_data_id); - } else if (this.data_source_id) { - queryResult = QueryResult.get(this.data_source_id, this.query, ttl); - } - - return queryResult; - }; - return Query; - }; + var Groups = function($resource) { - var DataSource = function ($resource) { - var DataSourceResource = $resource('/api/data_sources/:id', {id: '@id'}, {'get': {'method': 'GET', 'cache': true, 'isArray': true}}); + var GroupResource = $resource('/api/groups', {}, { + 'get': { + 'method': 'GET', + 'cache': true, + 'isArray': true + } + }) + GroupResource.prototype.get = function() { + return GroupResource.get(); + }; + return GroupResource; + } - return DataSourceResource; - } + var Group = function($resource) { + var Group = $resource('/api/groups/:id', { + id: '@id' + }); + Group.new = function(data) { + return new Group(data); + }; + return Group; + } - var Users = function ($resource) { - var UserResource = $resource('/api/users', {}, {'get': {'method': 'GET', 'cache': true, 'isArray': true}}) - UserResource.prototype.getUsers = function () { - return UserResource.get(); - }; - return UserResource; - } + var Table = function($resource) { + var Table = $resource('/api/tables', {}); + return Table; + } - - var Groups = function ($resource) { + var User = function($resource) { + var User = $resource('/api/users/:id', { + id: '@id' + }); + User.new = function(data) { + return new User(data); + }; + return User; + } - var GroupResource = $resource('/api/groups', {}, {'get': {'method': 'GET', 'cache': true, 'isArray': true}}) - GroupResource.prototype.get = function () { - return GroupResource.get(); - }; - return GroupResource; - } - - var Group = function ($resource) { - var Group = $resource('/api/groups/:id', {id: '@id'}); - - Group.new = function (data) { - return new Group(data); - }; - return Group; - } - - var Table = function ($resource) { - var Table = $resource('/api/tables', {}); - return Table; - } - - var User = function ($resource) { - var User = $resource('/api/users/:id', {id: '@id'}); - User.new = function (data) { - return new User(data); - }; - return User; - } + var Widget = function($resource, Query) { + var WidgetResource = $resource('/api/widgets/:id', { + id: '@id' + }); - var Widget = function ($resource, Query) { - var WidgetResource = $resource('/api/widgets/:id', {id: '@id'}); + WidgetResource.prototype.getQuery = function() { + if (!this.query && this.visualization) { + this.query = new Query(this.visualization.query); + } - WidgetResource.prototype.getQuery = function () { - if (!this.query && this.visualization) { - this.query = new Query(this.visualization.query); - } + return this.query; + }; - return this.query; - }; + WidgetResource.prototype.getName = function() { + if (this.visualization) { + return this.visualization.query.name + ' (' + this.visualization.name + ')'; + } + return _.str.truncate(this.text, 20); + }; - WidgetResource.prototype.getName = function () { - if (this.visualization) { - return this.visualization.query.name + ' (' + this.visualization.name + ')'; - } - return _.str.truncate(this.text, 20); - }; + return WidgetResource; + } - return WidgetResource; - } - - angular.module('redash.services') - .factory('QueryResult', ['$resource', '$timeout', QueryResult]) - .factory('Query', ['$resource', 'QueryResult', 'DataSource', Query]) - .factory('DataSource', ['$resource', DataSource]) - .factory('Groups', ['$resource', Groups]) - .factory('Group', ['$resource', Group]) - .factory('Users', ['$resource', Users]) - .factory('User', ['$resource', User]) - .factory('Widget', ['$resource', 'Query', Widget]) - .factory('Table', ['$resource', Table]); - -})(); + angular.module('redash.services') + .factory('QueryResult', ['$resource', '$timeout', QueryResult]) + .factory('Query', ['$resource', 'QueryResult', 'DataSource', Query]) + .factory('DataSource', ['$resource', DataSource]) + .factory('Groups', ['$resource', Groups]) + .factory('Group', ['$resource', Group]) + .factory('Users', ['$resource', Users]) + .factory('User', ['$resource', User]) + .factory('Widget', ['$resource', 'Query', Widget]) + .factory('Table', ['$resource', Table]); + +})(); \ No newline at end of file diff --git a/rd_ui/app/views/queries_delete_query_cell.html b/rd_ui/app/views/queries_delete_query_cell.html index 6008855637..abbb30fb0d 100644 --- a/rd_ui/app/views/queries_delete_query_cell.html +++ b/rd_ui/app/views/queries_delete_query_cell.html @@ -1 +1 @@ - + diff --git a/rd_ui/app/views/query.html b/rd_ui/app/views/query.html index 356adc44c5..5ebefb0ecb 100644 --- a/rd_ui/app/views/query.html +++ b/rd_ui/app/views/query.html @@ -126,7 +126,7 @@
- + Delete query diff --git a/redash/controllers.py b/redash/controllers.py index 4177dc781d..6f3fe4bcb3 100644 --- a/redash/controllers.py +++ b/redash/controllers.py @@ -375,9 +375,11 @@ def post(self): return {'widget': widget.to_dict(), 'layout': layout, 'new_row': new_row} class WidgetCheckApi(BaseResource): - def get(self, query_id): + def get(self, query_id): try: - widget = models.Widget.get(models.Widget.query_id == query_id) + visualization = models.Visualization.get(models.Visualization.query == query_id) + widget = models.Widget.get(models.Widget.visualization == visualization.id) + return {'widget': widget.to_dict()} except models.Widget.DoesNotExist: return {} diff --git a/redash/data/manager.py b/redash/data/manager.py index 225ed1994b..b74bdde407 100755 --- a/redash/data/manager.py +++ b/redash/data/manager.py @@ -88,10 +88,12 @@ def refresh_queries(self): # a reasonable assumption, but worth revisiting. # TODO: move this logic to the model. + outdated_queries = models.Query.select(peewee.Func('first_value', models.Query.id)\ .over(partition_by=[models.Query.query_hash, models.Query.data_source]))\ .join(models.QueryResult)\ - .where(models.Query.ttl > 0, + .where(models.Query.is_archived == False, + models.Query.ttl > 0, (models.QueryResult.retrieved_at + (models.Query.ttl * peewee.SQL("interval '1 second'"))) < peewee.SQL("(now() at time zone 'utc')")) diff --git a/redash/models.py b/redash/models.py index ff9b49df4e..32f811f65b 100644 --- a/redash/models.py +++ b/redash/models.py @@ -396,6 +396,7 @@ def to_dict(self, with_query=True): 'name': self.name, 'description': self.description, 'options': json.loads(self.options), + } if with_query: