Compare commits

..

13 Commits

Author SHA1 Message Date
Arik Fraimovich
18cc8434a0 Merge pull request #365 from EverythingMe/fix/mget_error
Fix: when no queries are being run, cleanup job fails with error
2015-01-27 18:39:26 +02:00
Arik Fraimovich
5eba318019 Fix: when no queries are being run, cleanup job fails with error 2015-01-27 18:23:21 +02:00
Arik Fraimovich
63274dbb17 Merge pull request #363 from EverythingMe/feature/query_parameters
Feature: support for query parameters
2015-01-27 18:22:29 +02:00
Arik Fraimovich
4c73e788ae Ability to set ttl (max age) from query string 2015-01-27 17:17:58 +02:00
Arik Fraimovich
b71a2b3651 Enable query params in dashboard 2015-01-27 17:00:21 +02:00
Arik Fraimovich
521a32dfff Merge pull request #364 from EverythingMe/fix/missing_text_widgets
Remove unneeded where clause which was preventing from text widgets to show.
2015-01-27 12:59:58 +02:00
Arik Fraimovich
fd6ebe6e12 Remove unneeded where clause which was preventing from text widgets to show. 2015-01-27 12:52:17 +02:00
Arik Fraimovich
6fb97675ad Add mustache to Karma conf 2015-01-27 11:28:51 +02:00
Arik Fraimovich
c0c102207d Initial work on support for query parameters 2015-01-27 10:28:11 +02:00
Arik Fraimovich
3b9d9ac75d Merge pull request #362 from joeysim/ctrl-s-for-save
added support for saving query with ctrl+s
2015-01-26 07:31:53 +02:00
Joey Simhon
2536fd57ed added support for saving query with cmd+s 2015-01-25 22:52:31 +02:00
Arik Fraimovich
d941e5e5b1 Merge pull request #361 from EverythingMe/DAT-825
Fix: fail with 403 when user not allowed to archive query.
2015-01-25 17:44:58 +02:00
Arik Fraimovich
febf9939c8 Fix: fail with 403 when user not allowed to archive query. 2015-01-25 17:30:10 +02:00
10 changed files with 95 additions and 30 deletions

View File

@@ -126,6 +126,7 @@
<script src="/scripts/ui-bootstrap-tpls-0.5.0.min.js"></script>
<script src="/bower_components/bucky/bucky.js"></script>
<script src="/bower_components/pace/pace.js"></script>
<script src="/bower_components/mustache/mustache.js"></script>
<!-- endbuild -->
<!-- build:js({.tmp,app}) /scripts/scripts.js -->

View File

@@ -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])
})();

View File

@@ -14,22 +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();
}
},
// 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);
@@ -44,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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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",

View File

@@ -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',

View File

@@ -349,7 +349,7 @@ class QueryAPI(BaseResource):
if q.user.id == self.current_user.id or self.current_user.has_permission('admin'):
q.archive()
else:
self.delete_others_query(query_id)
abort(403)
else:
abort(404, message="Query not found.")

View File

@@ -452,7 +452,6 @@ class Dashboard(BaseModel):
if with_widgets:
widgets = Widget.select(Widget, Visualization, Query, User)\
.where(Widget.dashboard == self.id)\
.where(Query.is_archived == False)\
.join(Visualization, join_type=peewee.JOIN_LEFT_OUTER)\
.join(Query, join_type=peewee.JOIN_LEFT_OUTER)\
.join(User, join_type=peewee.JOIN_LEFT_OUTER)

View File

@@ -171,8 +171,10 @@ def refresh_queries():
def cleanup_tasks():
# in case of cold restart of the workers, there might be jobs that still have their "lock" object, but aren't really
# going to run. this job removes them.
lock_keys = redis_connection.keys("query_hash_job:*") # TODO: use set instead of keys command
if not lock_keys:
return
query_tasks = [QueryTask(job_id=j) for j in redis_connection.mget(lock_keys)]
logger.info("Found %d locks", len(query_tasks))