Skip to content

Commit

Permalink
Merge pull request #126 from EverythingMe/query-view-page
Browse files Browse the repository at this point in the history
Fixes #121: redesign query page (have separate page for editing and viewing)
  • Loading branch information
arikfr committed Mar 11, 2014
2 parents 6ee4e6c + 6c00b8a commit f420c91
Show file tree
Hide file tree
Showing 14 changed files with 590 additions and 32 deletions.
21 changes: 12 additions & 9 deletions rd_ui/app/index.html
Expand Up @@ -97,31 +97,34 @@
<script src="/bower_components/angular-ui-codemirror/ui-codemirror.js"></script>
<script src="/bower_components/highcharts/highcharts.js"></script>
<script src="/bower_components/highcharts/modules/exporting.js"></script>
<script src="/scripts/ng-highchart.js"></script>
<script src="/scripts/smart-table.js"></script>
<script src="/scripts/ui-bootstrap-tpls-0.5.0.min.js"></script>
<script src="/bower_components/gridster/dist/jquery.gridster.js"></script>
<script src="/bower_components/angular-growl/build/angular-growl.js"></script>
<script src="/bower_components/pivottable/examples/pivot.js"></script>
<script src="/bower_components/cornelius/src/cornelius.js"></script>
<script src="/bower_components/mousetrap/mousetrap.js"></script>
<script src="/bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.js"></script>

<script src="/scripts/ng_highchart.js"></script>
<script src="/scripts/ng_smart_table.js"></script>
<script src="/scripts/ui-bootstrap-tpls-0.5.0.min.js"></script>

<!-- endbuild -->

<!-- build:js({.tmp,app}) /scripts/scripts.js -->
<script src="/scripts/app.js"></script>
<script src="/scripts/controllers.js"></script>
<script src="/scripts/services/services.js"></script>
<script src="/scripts/services/notifications.js"></script>
<script src="/scripts/services/dashboards.js"></script>
<script src="/scripts/controllers/controllers.js"></script>
<script src="/scripts/controllers/admin_controllers.js"></script>
<script src="/scripts/controllers/query_view.js"></script>
<script src="/scripts/visualizations/base.js"></script>
<script src="/scripts/visualizations/chart.js"></script>
<script src="/scripts/visualizations/cohort.js"></script>
<script src="/scripts/visualizations/table.js"></script>
<script src="/scripts/admin_controllers.js"></script>
<script src="/scripts/visualizations/pivot.js"></script>
<script src="/scripts/directives.js"></script>
<script src="/scripts/services.js"></script>
<script src="/scripts/filters.js"></script>
<script src="/scripts/services/notifications.js"></script>
<script src="/scripts/services/dashboards.js"></script>
<script src="/scripts/query_fiddle/renderers.js"></script>
<!-- endbuild -->

<script>
Expand Down
44 changes: 41 additions & 3 deletions rd_ui/app/scripts/app.js
Expand Up @@ -17,6 +17,18 @@ angular.module('redash', [
]).config(['$routeProvider', '$locationProvider', '$compileProvider', 'growlProvider',
function($routeProvider, $locationProvider, $compileProvider, growlProvider) {

function getQuery(Query, $q, $route) {
var defer = $q.defer();

Query.get({
'id': $route.current.params.queryId
}, function(query) {
defer.resolve(query);
});

return defer.promise;
}

$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|http|data):/);
$locationProvider.html5Mode(true);
growlProvider.globalTimeToLive(2000);
Expand All @@ -31,15 +43,41 @@ angular.module('redash', [
reloadOnSearch: false
});
$routeProvider.when('/queries/new', {
templateUrl: '/views/queryfiddle.html',
controller: 'QueryFiddleCtrl',
reloadOnSearch: false
templateUrl: '/views/queryview.html',
controller: 'QueryViewCtrl',
reloadOnSearch: false,
resolve: {
'viewSource': function isViewSource() {
return true;
}
}
});
// TODO
// we should have 2 controllers: queryViewCtrl and queryEditCtrl
$routeProvider.when('/queries/:queryId', {
templateUrl: '/views/queryview.html',
controller: 'QueryViewCtrl',
reloadOnSearch: false,
resolve: {
'query': getQuery
}
});
$routeProvider.when('/queries/:queryId/fiddle', {
templateUrl: '/views/queryfiddle.html',
controller: 'QueryFiddleCtrl',
reloadOnSearch: false
});
$routeProvider.when('/queries/:queryId/source', {
templateUrl: '/views/queryview.html',
controller: 'QueryViewCtrl',
reloadOnSearch: false,
resolve: {
'query': getQuery,
'viewSource': function isViewSource() {
return true;
}
}
});
$routeProvider.when('/admin/status', {
templateUrl: '/views/admin_status.html',
controller: 'AdminStatusCtrl'
Expand Down
File renamed without changes.
File renamed without changes.
282 changes: 282 additions & 0 deletions rd_ui/app/scripts/controllers/query_view.js
@@ -0,0 +1,282 @@
(function () {
'use strict';

var QueryViewCtrl = function ($scope, $window, $route, $http, $location, growl, notifications, Query, Visualization) {
var DEFAULT_TAB = 'table';
var pristineHash = "";
var leavingPageText = "You will lose your changes if you leave";
var route = $route.current;

$scope.dirty = undefined;
$scope.isNewQuery = false;
$scope.isOwner = false;
$scope.canEdit = false;
$scope.canFork = false;

$scope.isSourceVisible = route.locals.viewSource;

$scope.queryExecuting = false;

$scope.newVisualization = undefined;

updateSourceHref();

$window.onbeforeunload = function () {
if ($scope.canEdit && $scope.dirty) {
return leavingPageText;
}
}

function updateSourceHref() {
if ($scope.query && $scope.query.id) {
$scope.sourceHref = $scope.isSourceVisible ?
$location.url().replace('source', '') : getQuerySourceUrl($scope.query.id);
}
};

function getQuerySourceUrl(queryId) {
return '/queries/' + queryId + '/source#' + $location.hash();
};

Mousetrap.bindGlobal("meta+s", function (e) {
e.preventDefault();

if ($scope.canEdit) {
$scope.saveQuery();
}
});

$scope.$on('$locationChangeStart', function (event, next, current) {
if (next.split("#")[0] == current.split("#")[0]) {
return;
}

if (!$scope.canEdit) {
return;
}

if ($scope.dirty && !confirm(leavingPageText + "\n\nAre you sure you want to leave this page?")) {
event.preventDefault();
} else {
Mousetrap.unbind("meta+s");
}
});

$scope.$watch(function () {
return $location.hash()
}, function (hash) {
$scope.selectedTab = hash || DEFAULT_TAB;
updateSourceHref();
});

$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.addSuccessMessage("Query forked");
} else {
growl.addSuccessMessage("Query saved");
}

if (oldId != q.id) {
$location.url(getQuerySourceUrl(q.id)).replace();
}
}, 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);
}
});

// view or source pages: controller is instantiated with a query
if (route.locals.query) {
$scope.query = route.locals.query;
pristineHash = $scope.query.getHash();
$scope.dirty = false;
$scope.queryResult = $scope.query.getQueryResult();

$scope.isOwner = currentUser.canEdit($scope.query);
$scope.canEdit = $scope.isSourceVisible && $scope.isOwner;
$scope.canFork = true;

} else {
// new query
$scope.query = new Query({
query: "",
name: "New Query",
ttl: -1,
user: currentUser
});
$scope.lockButton(false);
$scope.isOwner = $scope.canEdit = true;
$scope.isNewQuery = true;
}

$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;
$location.hash($scope.selectedTab);
}
$scope.query.visualizations =
$scope.query.visualizations.filter(function (v) {
return vis.id !== v.id;
});
}
};

var unbind = $scope.$watch('selectedTab == "add"', function (newPanel) {
if (newPanel && route.params.queryId == undefined) {
unbind();
$scope.saveQuery();
}
});
};

angular.module('redash.controllers').controller('QueryViewCtrl',
[
'$scope',
'$window',
'$route',
'$http',
'$location',
'growl',
'notifications',
'Query',
'Visualization',
QueryViewCtrl
]);

})();

0 comments on commit f420c91

Please sign in to comment.