From f8280552a0f56d7d4dd9fda03d154ee2da339cf6 Mon Sep 17 00:00:00 2001 From: Amir Nissim Date: Tue, 4 Mar 2014 16:17:52 +0200 Subject: [PATCH] #121: QueryViewCtrl with 'strict mode' --- rd_ui/app/index.html | 1 + rd_ui/app/scripts/app.js | 10 +- rd_ui/app/scripts/controllers/QueryView.js | 262 +++++++++++++++++++++ 3 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 rd_ui/app/scripts/controllers/QueryView.js diff --git a/rd_ui/app/index.html b/rd_ui/app/index.html index 05145c8c5..260c29081 100644 --- a/rd_ui/app/index.html +++ b/rd_ui/app/index.html @@ -111,6 +111,7 @@ + diff --git a/rd_ui/app/scripts/app.js b/rd_ui/app/scripts/app.js index 205b65de1..a68f92e20 100644 --- a/rd_ui/app/scripts/app.js +++ b/rd_ui/app/scripts/app.js @@ -35,16 +35,16 @@ angular.module('redash', [ controller: 'QueryFiddleCtrl', reloadOnSearch: false }); - $routeProvider.when('/queries/view/:queryId', { - templateUrl: '/views/queryview.html', - controller: 'QueryFiddleCtrl', - reloadOnSearch: false - }); $routeProvider.when('/queries/:queryId', { templateUrl: '/views/queryfiddle.html', controller: 'QueryFiddleCtrl', reloadOnSearch: false }); + $routeProvider.when('/queries/view/:queryId', { + templateUrl: '/views/queryview.html', + controller: 'QueryViewCtrl', + reloadOnSearch: false + }); $routeProvider.when('/admin/status', { templateUrl: '/views/admin_status.html', controller: 'AdminStatusCtrl' diff --git a/rd_ui/app/scripts/controllers/QueryView.js b/rd_ui/app/scripts/controllers/QueryView.js new file mode 100644 index 000000000..3b381f433 --- /dev/null +++ b/rd_ui/app/scripts/controllers/QueryView.js @@ -0,0 +1,262 @@ +(function() { + 'use strict'; + + var QueryViewCtrl = function($scope, $window, $routeParams, $http, $location, growl, notifications, Query, Visualization) { + var DEFAULT_TAB = 'table'; + var pristineHash = null; + var leavingPageText = "You will lose your changes if you leave"; + + $scope.dirty = undefined; + $scope.newVisualization = undefined; + + $window.onbeforeunload = function() { + if (currentUser.canEdit($scope.query) && $scope.dirty) { + return leavingPageText; + } + } + + Mousetrap.bindGlobal("meta+s", function(e) { + e.preventDefault(); + + if (currentUser.canEdit($scope.query)) { + $scope.saveQuery(); + } + }); + + $scope.$on('$locationChangeStart', function(event, next, current) { + if (next.split("#")[0] == current.split("#")[0]) { + return; + } + + if (!currentUser.canEdit($scope.query)) { + return; + } + + if ($scope.dirty && !confirm(leavingPageText + "\n\nAre you sure you want to leave this page?")) { + event.preventDefault(); + } else { + Mousetrap.unbind("meta+s"); + } + }); + + $scope.$parent.pageTitle = "Query Fiddle"; + + $scope.$watch(function() { + return $location.hash() + }, function(hash) { + $scope.selectedTab = hash || DEFAULT_TAB; + }); + + $scope.lockButton = function(lock) { + $scope.queryExecuting = lock; + }; + + $scope.formatQuery = function() { + $scope.editorOptions.readOnly = 'nocursor'; + + $http.post('/api/queries/format', { + 'query': $scope.query.query + }).success(function(response) { + $scope.query.query = response; + $scope.editorOptions.readOnly = false; + }) + } + + $scope.saveQuery = function(duplicate, oldId) { + if (!oldId) { + oldId = $scope.query.id; + } + + delete $scope.query.latest_query_data; + $scope.query.$save(function(q) { + pristineHash = q.getHash(); + $scope.dirty = false; + + if (duplicate) { + growl.addInfoMessage("Query duplicated.", { + ttl: 2000 + }); + } else { + growl.addSuccessMessage("Query saved.", { + ttl: 2000 + }); + } + + if (oldId != q.id) { + if (oldId == undefined) { + $location.path($location.path().replace('new', q.id)).replace(); + } else { + // TODO: replace this with a safer method + $location.path($location.path().replace(oldId, q.id)).replace(); + + // Reset visualizations tab to table after duplicating a query: + $location.hash('table'); + } + } + }, function(httpResponse) { + growl.addErrorMessage("Query could not be saved"); + }); + }; + + $scope.duplicateQuery = function() { + var oldId = $scope.query.id; + $scope.query.id = null; + $scope.query.ttl = -1; + + $scope.saveQuery(true, oldId); + }; + + // Query Editor: + $scope.editorOptions = { + mode: 'text/x-sql', + lineWrapping: true, + lineNumbers: true, + readOnly: false, + matchBrackets: true, + autoCloseBrackets: true + }; + + $scope.refreshOptions = [{ + value: -1, + name: 'No Refresh' + }, { + value: 60, + name: 'Every minute' + }, ] + + _.each(_.range(1, 13), function(i) { + $scope.refreshOptions.push({ + value: i * 3600, + name: 'Every ' + i + 'h' + }); + }) + + $scope.refreshOptions.push({ + value: 24 * 3600, + name: 'Every 24h' + }); + $scope.refreshOptions.push({ + value: 7 * 24 * 3600, + name: 'Once a week' + }); + + $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; + } + + 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); + } + }); + + if ($routeParams.queryId != undefined) { + $scope.query = Query.get({ + id: $routeParams.queryId + }, function(q) { + pristineHash = q.getHash(); + $scope.dirty = false; + $scope.queryResult = $scope.query.getQueryResult(); + }); + } else { + $scope.query = new Query({ + query: "", + name: "New Query", + ttl: -1, + user: currentUser + }); + $scope.lockButton(false); + } + + $scope.$watch('query.name', function() { + $scope.$parent.pageTitle = $scope.query.name; + }); + + $scope.$watch(function() { + return $scope.query.getHash(); + }, function(newHash) { + $scope.dirty = (newHash !== pristineHash); + }); + + $scope.executeQuery = function() { + $scope.queryResult = $scope.query.getQueryResult(0); + $scope.lockButton(true); + $scope.cancelling = false; + }; + + $scope.cancelExecution = function() { + $scope.cancelling = true; + $scope.queryResult.cancelExecution(); + }; + + $scope.deleteVisualization = function($e, vis) { + $e.preventDefault(); + if (confirm('Are you sure you want to delete ' + vis.name + ' ?')) { + Visualization.delete(vis); + if ($scope.selectedTab == vis.id) { + $scope.selectedTab = DEFAULT_TAB; + } + $scope.query.visualizations = + $scope.query.visualizations.filter(function(v) { + return vis.id !== v.id; + }); + } + }; + + var unbind = $scope.$watch('selectedTab == "add"', function(newPanel) { + if (newPanel && $routeParams.queryId == undefined) { + unbind(); + $scope.saveQuery(); + } + }); + }; + + angular.module('redash.controllers').controller('QueryViewCtrl', + [ + '$scope', + '$window', + '$routeParams', + '$http', + '$location', + 'growl', + 'notifications', + 'Query', + 'Visualization', + QueryViewCtrl + ]); + +})(); \ No newline at end of file