diff --git a/rd_ui/app/scripts/controllers/query_view.js b/rd_ui/app/scripts/controllers/query_view.js index df8a8d4d..14c35d00 100644 --- a/rd_ui/app/scripts/controllers/query_view.js +++ b/rd_ui/app/scripts/controllers/query_view.js @@ -107,19 +107,6 @@ } $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) { diff --git a/rd_ui/app/scripts/directives/directives.js b/rd_ui/app/scripts/directives/directives.js index 342f53cb..9bef5f1a 100644 --- a/rd_ui/app/scripts/directives/directives.js +++ b/rd_ui/app/scripts/directives/directives.js @@ -1,219 +1,227 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - var directives = angular.module('redash.directives', []); + var directives = angular.module('redash.directives', []); - directives.directive('alertUnsavedChanges', ['$window', function($window) { - return { - restrict: 'E', - replace: true, - scope: { - 'isDirty': '=' - }, - link: function($scope) { - var + directives.directive('alertUnsavedChanges', ['$window', function ($window) { + return { + restrict: 'E', + replace: true, + scope: { + 'isDirty': '=' + }, + link: function ($scope) { + var - unloadMessage = "You will lose your changes if you leave", - confirmMessage = unloadMessage + "\n\nAre you sure you want to leave this page?", + unloadMessage = "You will lose your changes if you leave", + confirmMessage = unloadMessage + "\n\nAre you sure you want to leave this page?", - // store original handler (if any) - _onbeforeunload = $window.onbeforeunload; + // store original handler (if any) + _onbeforeunload = $window.onbeforeunload; - $window.onbeforeunload = function() { - return $scope.isDirty ? unloadMessage : null; - } - - $scope.$on('$locationChangeStart', function(event, next, current) { - if (next.split("#")[0] == current.split("#")[0]) { - return; - } - - if ($scope.isDirty && !confirm(confirmMessage)) { - event.preventDefault(); - } - }); - - $scope.$on('$destroy', function() { - $window.onbeforeunload = _onbeforeunload; - }); - } + $window.onbeforeunload = function () { + return $scope.isDirty ? unloadMessage : null; } - }]); - directives.directive('rdTab', function() { - return { - restrict: 'E', - scope: { - 'tabId': '@', - 'name': '@' - }, - transclude: true, - template: '
  • {{name}}
  • ', - replace: true, - link: function(scope) { - scope.$watch(function(){return scope.$parent.selectedTab}, function(tab) { - scope.selectedTab = tab; - }); - } + $scope.$on('$locationChangeStart', function (event, next, current) { + if (next.split("#")[0] == current.split("#")[0]) { + return; + } + + if ($scope.isDirty && !confirm(confirmMessage)) { + event.preventDefault(); + } + }); + + $scope.$on('$destroy', function () { + $window.onbeforeunload = _onbeforeunload; + }); + } + } + }]); + + directives.directive('rdTab', function () { + return { + restrict: 'E', + scope: { + 'tabId': '@', + 'name': '@' + }, + transclude: true, + template: '
  • {{name}}
  • ', + replace: true, + link: function (scope) { + scope.$watch(function () { + return scope.$parent.selectedTab + }, function (tab) { + scope.selectedTab = tab; + }); + } + } + }); + + directives.directive('rdTabs', ['$location', function ($location) { + return { + restrict: 'E', + scope: { + tabsCollection: '=', + selectedTab: '=' + }, + template: '', + replace: true, + link: function ($scope, element, attrs) { + $scope.selectTab = function (tabKey) { + $scope.selectedTab = _.find($scope.tabsCollection, function (tab) { + return tab.key == tabKey; + }); } - }); - directives.directive('rdTabs', ['$location', function($location) { - return { - restrict: 'E', - scope: { - tabsCollection: '=', - selectedTab: '=' - }, - template: '', - replace: true, - link: function($scope, element, attrs) { - $scope.selectTab = function(tabKey) { - $scope.selectedTab = _.find($scope.tabsCollection, function(tab) { return tab.key == tabKey; }); - } + $scope.$watch(function () { + return $location.hash() + }, function (hash) { + if (hash) { + $scope.selectTab($location.hash()); + } else { + $scope.selectTab($scope.tabsCollection[0].key); + } + }); + } + } + }]); - $scope.$watch(function() { return $location.hash()}, function(hash) { - if (hash) { - $scope.selectTab($location.hash()); - } else { - $scope.selectTab($scope.tabsCollection[0].key); - } - }); - } - } - }]); + // From: http://jsfiddle.net/joshdmiller/NDFHg/ + directives.directive('editInPlace', function () { + return { + restrict: 'E', + scope: { + value: '=', + ignoreBlanks: '=', + editable: '=', + done: '=' + }, + template: function (tElement, tAttrs) { + var elType = tAttrs.editor || 'input'; + var placeholder = tAttrs.placeholder || 'Click to edit'; + return '' + + '' + placeholder + '' + + '<{elType} ng-model="value" class="rd-form-control">'.replace('{elType}', elType); + }, + link: function ($scope, element, attrs) { + // Let's get a reference to the input element, as we'll want to reference it. + var inputElement = angular.element(element.children()[2]); - // From: http://jsfiddle.net/joshdmiller/NDFHg/ - directives.directive('editInPlace', function () { - return { - restrict: 'E', - scope: { - value: '=', - ignoreBlanks: '=', - editable: '=', - done: '=' - }, - template: function(tElement, tAttrs) { - var elType = tAttrs.editor || 'input'; - var placeholder = tAttrs.placeholder || 'Click to edit'; - return '' + - '' + placeholder + '' + - '<{elType} ng-model="value" class="rd-form-control">'.replace('{elType}', elType); - }, - link: function ($scope, element, attrs) { - // Let's get a reference to the input element, as we'll want to reference it. - var inputElement = angular.element(element.children()[2]); + // This directive should have a set class so we can style it. + element.addClass('edit-in-place'); - // This directive should have a set class so we can style it. - element.addClass('edit-in-place'); + // Initially, we're not editing. + $scope.editing = false; - // Initially, we're not editing. - $scope.editing = false; + // ng-click handler to activate edit-in-place + $scope.edit = function () { + $scope.oldValue = $scope.value; - // ng-click handler to activate edit-in-place - $scope.edit = function () { - $scope.oldValue = $scope.value; + $scope.editing = true; - $scope.editing = true; + // We control display through a class on the directive itself. See the CSS. + element.addClass('active'); - // We control display through a class on the directive itself. See the CSS. - element.addClass('active'); - - // And we must focus the element. - // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function, - // we have to reference the first element in the array. - inputElement[0].focus(); - }; - - function save() { - if ($scope.editing) { - if ($scope.ignoreBlanks && _.isEmpty($scope.value)) { - $scope.value = $scope.oldValue; - } - $scope.editing = false; - element.removeClass('active'); - - if ($scope.value !== $scope.oldValue) { - $scope.done && $scope.done(); - } - } - } - - $(inputElement).keydown(function(e) { - // 'return' or 'enter' key pressed - // allow 'shift' to break lines - if (e.which === 13 && !e.shiftKey) { - save(); - } else if (e.which === 27) { - $scope.value = $scope.oldValue; - $scope.$apply(function() { - $(inputElement[0]).blur(); - }); - } - }).blur(function() { - save(); - }); - } + // And we must focus the element. + // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function, + // we have to reference the first element in the array. + inputElement[0].focus(); }; - }); - // http://stackoverflow.com/a/17904092/1559840 - directives.directive('jsonText', function() { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, element, attr, ngModel) { - function into(input) { - return JSON.parse(input); - } - function out(data) { - return JSON.stringify(data, undefined, 2); - } - ngModel.$parsers.push(into); - ngModel.$formatters.push(out); - - scope.$watch(attr.ngModel, function(newValue) { - element[0].value = out(newValue); - }, true); + function save() { + if ($scope.editing) { + if ($scope.ignoreBlanks && _.isEmpty($scope.value)) { + $scope.value = $scope.oldValue; } - }; - }); + $scope.editing = false; + element.removeClass('active'); - directives.directive('rdTimer', [function () { - return { - restrict: 'E', - scope: { timestamp: '=' }, - template: '{{currentTime}}', - controller: ['$scope' ,function ($scope) { - $scope.currentTime = "00:00:00"; - - // We're using setInterval directly instead of $timeout, to avoid using $apply, to - // prevent the digest loop being run every second. - var currentTimer = setInterval(function() { - $scope.currentTime = moment(moment() - moment($scope.timestamp)).utc().format("HH:mm:ss"); - $scope.$digest(); - }, 1000); - - $scope.$on('$destroy', function () { - if (currentTimer) { - clearInterval(currentTimer); - currentTimer = null; - } - }); - }] - }; - }]); - - directives.directive('rdTimeAgo', function() { - return { - restrict: 'E', - scope: { - value: '=' - }, - template: '' + - '' + - '-' + - '' + if ($scope.value !== $scope.oldValue) { + $scope.done && $scope.done(); + } + } } - }); + + $(inputElement).keydown(function (e) { + // 'return' or 'enter' key pressed + // allow 'shift' to break lines + if (e.which === 13 && !e.shiftKey) { + save(); + } else if (e.which === 27) { + $scope.value = $scope.oldValue; + $scope.$apply(function () { + $(inputElement[0]).blur(); + }); + } + }).blur(function () { + save(); + }); + } + }; + }); + + // http://stackoverflow.com/a/17904092/1559840 + directives.directive('jsonText', function () { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attr, ngModel) { + function into(input) { + return JSON.parse(input); + } + + function out(data) { + return JSON.stringify(data, undefined, 2); + } + + ngModel.$parsers.push(into); + ngModel.$formatters.push(out); + + scope.$watch(attr.ngModel, function (newValue) { + element[0].value = out(newValue); + }, true); + } + }; + }); + + directives.directive('rdTimer', [function () { + return { + restrict: 'E', + scope: { timestamp: '=' }, + template: '{{currentTime}}', + controller: ['$scope' , function ($scope) { + $scope.currentTime = "00:00:00"; + + // We're using setInterval directly instead of $timeout, to avoid using $apply, to + // prevent the digest loop being run every second. + var currentTimer = setInterval(function () { + $scope.currentTime = moment(moment() - moment($scope.timestamp)).utc().format("HH:mm:ss"); + $scope.$digest(); + }, 1000); + + $scope.$on('$destroy', function () { + if (currentTimer) { + clearInterval(currentTimer); + currentTimer = null; + } + }); + }] + }; + }]); + + directives.directive('rdTimeAgo', function () { + return { + restrict: 'E', + scope: { + value: '=' + }, + template: '' + + '' + + '-' + + '' + } + }); })(); diff --git a/rd_ui/app/scripts/directives/query_directives.js b/rd_ui/app/scripts/directives/query_directives.js index b8f29eec..af376ff2 100644 --- a/rd_ui/app/scripts/directives/query_directives.js +++ b/rd_ui/app/scripts/directives/query_directives.js @@ -38,6 +38,26 @@ } } + function queryResultCSVLink() { + return { + restrict: 'A', + link: function (scope, element) { + scope.$watch('queryResult && queryResult.getData()', function(data) { + if (!data) { + return; + } + + if (scope.queryResult.getId() == null) { + element.attr('href', ''); + } else { + element.attr('href', '/api/queries/' + scope.query.id + '/results/' + scope.queryResult.getId() + '.csv'); + element.attr('download', scope.query.name.replace(" ", "_") + moment(scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + ".csv"); + } + }); + } + } + } + function queryEditor() { return { restrict: 'E', @@ -135,6 +155,7 @@ angular.module('redash.directives') .directive('queryLink', queryLink) .directive('querySourceLink', querySourceLink) + .directive('queryResultLink', queryResultCSVLink) .directive('queryEditor', queryEditor) .directive('queryRefreshSelect', queryRefreshSelect) .directive('queryFormatter', ['$http', queryFormatter]); diff --git a/rd_ui/app/views/dashboard.html b/rd_ui/app/views/dashboard.html index a2c0f7c0..4bd67b90 100644 --- a/rd_ui/app/views/dashboard.html +++ b/rd_ui/app/views/dashboard.html @@ -44,6 +44,12 @@ + + + + + + diff --git a/rd_ui/app/views/query.html b/rd_ui/app/views/query.html index 5142b708..63b03f11 100644 --- a/rd_ui/app/views/query.html +++ b/rd_ui/app/views/query.html @@ -122,7 +122,7 @@

    - + Download Dataset