diff --git a/rd_ui/app/index.html b/rd_ui/app/index.html
index 1f5b8ce8a1..226fb06053 100644
--- a/rd_ui/app/index.html
+++ b/rd_ui/app/index.html
@@ -126,6 +126,7 @@
+
diff --git a/rd_ui/app/scripts/controllers/dashboard.js b/rd_ui/app/scripts/controllers/dashboard.js
index dfe52eb2a7..08ab3e1ea0 100644
--- a/rd_ui/app/scripts/controllers/dashboard.js
+++ b/rd_ui/app/scripts/controllers/dashboard.js
@@ -16,7 +16,7 @@
var w = new Widget(widget);
if (w.visualization) {
- promises.push(w.getQuery().getQueryResultPromise());
+ promises.push(w.getQuery().getQueryResult().toPromise());
}
return w;
@@ -104,7 +104,7 @@
};
};
- var WidgetCtrl = function($scope, Events, Query) {
+ var WidgetCtrl = function($scope, $location, Events, Query) {
$scope.deleteWidget = function() {
if (!confirm('Are you sure you want to remove "' + $scope.widget.getName() + '" from the dashboard?')) {
return;
@@ -128,7 +128,9 @@
Events.record(currentUser, "view", "visualization", $scope.widget.visualization.id);
$scope.query = $scope.widget.getQuery();
- $scope.queryResult = $scope.query.getQueryResult();
+ var parameters = Query.collectParamsFromQueryString($location, $scope.query);
+ var maxAge = $location.search()['maxAge'];
+ $scope.queryResult = $scope.query.getQueryResult(maxAge, parameters);
$scope.nextUpdateTime = moment(new Date(($scope.query.updated_at + $scope.query.ttl + $scope.query.runtime + 300) * 1000)).fromNow();
$scope.type = 'visualization';
@@ -139,6 +141,6 @@
angular.module('redash.controllers')
.controller('DashboardCtrl', ['$scope', 'Events', 'Widget', '$routeParams', '$location', '$http', '$timeout', '$q', 'Dashboard', DashboardCtrl])
- .controller('WidgetCtrl', ['$scope', 'Events', 'Query', WidgetCtrl])
+ .controller('WidgetCtrl', ['$scope', '$location', 'Events', 'Query', WidgetCtrl])
})();
diff --git a/rd_ui/app/scripts/controllers/query_source.js b/rd_ui/app/scripts/controllers/query_source.js
index 9caa7b9ca7..084c19bbfe 100644
--- a/rd_ui/app/scripts/controllers/query_source.js
+++ b/rd_ui/app/scripts/controllers/query_source.js
@@ -14,27 +14,7 @@
var isNewQuery = !$scope.query.id,
queryText = $scope.query.query,
// ref to QueryViewCtrl.saveQuery
- saveQuery = $scope.saveQuery,
- shortcuts = {
- 'meta+s': function () {
- if ($scope.canEdit) {
- $scope.saveQuery();
- }
- },
- 'ctrl+s': function () {
- if ($scope.canEdit) {
- $scope.saveQuery();
- }
- },
- // Cmd+Enter for Mac
- 'meta+enter': function () {
- $scope.executeQuery();
- },
- // Ctrl+Enter for PC
- 'ctrl+enter': function () {
- $scope.executeQuery();
- }
- };
+ saveQuery = $scope.saveQuery;
$scope.sourceMode = true;
$scope.canEdit = currentUser.canEdit($scope.query);
@@ -49,8 +29,22 @@
}
});
-
- KeyboardShortcuts.bind(shortcuts);
+ KeyboardShortcuts.bind({
+ 'meta+s': function () {
+ if ($scope.canEdit) {
+ $scope.saveQuery();
+ }
+ },
+ 'ctrl+s': function () {
+ if ($scope.canEdit) {
+ $scope.saveQuery();
+ }
+ },
+ // Cmd+Enter for Mac
+ 'meta+enter': $scope.executeQuery,
+ // Ctrl+Enter for PC
+ 'ctrl+enter': $scope.executeQuery
+ });
// @override
$scope.saveQuery = function(options, data) {
diff --git a/rd_ui/app/scripts/controllers/query_view.js b/rd_ui/app/scripts/controllers/query_view.js
index 67e6155c7c..5ce8a22da0 100644
--- a/rd_ui/app/scripts/controllers/query_view.js
+++ b/rd_ui/app/scripts/controllers/query_view.js
@@ -4,9 +4,18 @@
function QueryViewCtrl($scope, Events, $route, $location, notifications, growl, Query, DataSource) {
var DEFAULT_TAB = 'table';
+ var getQueryResult = function(ttl) {
+ // Collect params, and getQueryResult with params; getQueryResult merges it into the query
+ var parameters = Query.collectParamsFromQueryString($location, $scope.query);
+ if (ttl == undefined) {
+ ttl = $location.search()['maxAge'];
+ }
+ $scope.queryResult = $scope.query.getQueryResult(ttl, parameters);
+ }
+
$scope.query = $route.current.locals.query;
Events.record(currentUser, 'view', 'query', $scope.query.id);
- $scope.queryResult = $scope.query.getQueryResult();
+ getQueryResult();
$scope.queryExecuting = false;
$scope.isQueryOwner = currentUser.id === $scope.query.user.id;
@@ -57,7 +66,7 @@
};
$scope.executeQuery = function() {
- $scope.queryResult = $scope.query.getQueryResult(0);
+ getQueryResult(0);
$scope.lockButton(true);
$scope.cancelling = false;
Events.record(currentUser, 'execute', 'query', $scope.query.id);
diff --git a/rd_ui/app/scripts/services/resources.js b/rd_ui/app/scripts/services/resources.js
index fae28acf4a..8e87d387e0 100644
--- a/rd_ui/app/scripts/services/resources.js
+++ b/rd_ui/app/scripts/services/resources.js
@@ -399,15 +399,54 @@
});
};
+ Query.collectParamsFromQueryString = function($location, query) {
+ var parameterNames = query.getParameters();
+ var parameters = {};
+
+ var queryString = $location.search();
+ _.each(parameterNames, function(param, i) {
+ var qsName = "p_" + param;
+ if (qsName in queryString) {
+ parameters[param] = queryString[qsName];
+ }
+ });
+
+ return parameters;
+ };
+
Query.prototype.getSourceLink = function () {
return '/queries/' + this.id + '/source';
};
- Query.prototype.getQueryResult = function (ttl) {
+ Query.prototype.getQueryResult = function (ttl, parameters) {
if (ttl == undefined) {
ttl = this.ttl;
}
+ var queryText = this.query;
+
+ var queryParameters = this.getParameters();
+ var paramsRequired = !_.isEmpty(queryParameters);
+
+ var missingParams = parameters === undefined ? queryParameters : _.difference(queryParameters, _.keys(parameters));
+
+ if (paramsRequired && missingParams.length > 0) {
+ var paramsWord = "parameter";
+ if (missingParams.length > 1) {
+ paramsWord = "parameters";
+ }
+
+ return new QueryResult({job: {error: "Missing values for " + missingParams.join(', ') + " "+paramsWord+".", status: 4}});
+ }
+
+ if (parameters !== undefined) {
+ queryText = Mustache.render(queryText, parameters);
+
+ // Need to clear latest results, to make sure we don't used results for different params.
+ this.latest_query_data = null;
+ this.latest_query_data_id = null;
+ }
+
if (this.latest_query_data && ttl != 0) {
if (!this.queryResult) {
this.queryResult = new QueryResult({'query_result': this.latest_query_data});
@@ -417,7 +456,7 @@
this.queryResult = QueryResult.getById(this.latest_query_data_id);
}
} else if (this.data_source_id) {
- this.queryResult = QueryResult.get(this.data_source_id, this.query, ttl);
+ this.queryResult = QueryResult.get(this.data_source_id, queryText, ttl);
}
return this.queryResult;
@@ -425,6 +464,18 @@
Query.prototype.getQueryResultPromise = function() {
return this.getQueryResult().toPromise();
+ };
+
+ Query.prototype.getParameters = function() {
+ var parts = Mustache.parse(this.query);
+ var parameters = [];
+ _.each(parts, function(part) {
+ if (part[0] == 'name') {
+ parameters.push(part[1]);
+ }
+ });
+
+ return parameters;
}
return Query;
diff --git a/rd_ui/bower.json b/rd_ui/bower.json
index e80ca75b99..b20f51b3d1 100644
--- a/rd_ui/bower.json
+++ b/rd_ui/bower.json
@@ -27,7 +27,8 @@
"bucky": "~0.2.6",
"pace": "~0.5.1",
"angular-ui-select": "0.8.2",
- "font-awesome": "~4.2.0"
+ "font-awesome": "~4.2.0",
+ "mustache": "~1.0.0"
},
"devDependencies": {
"angular-mocks": "1.2.18",
diff --git a/rd_ui/test/karma.conf.js b/rd_ui/test/karma.conf.js
index c8d63a9c97..c5cc4ebc50 100644
--- a/rd_ui/test/karma.conf.js
+++ b/rd_ui/test/karma.conf.js
@@ -55,6 +55,7 @@ module.exports = function(config) {
'app/scripts/ui-bootstrap-tpls-0.5.0.min.js',
'app/bower_components/bucky/bucky.js',
'app/bower_components/pace/pace.js',
+ 'app/bower_components/mustache/mustache.js',
'app/scripts/app.js',
'app/scripts/services/services.js',