Skip to content

Commit

Permalink
Merge pull request #400 from EverythingMe/feature/better_scheduler
Browse files Browse the repository at this point in the history
Improved query scheduling option
  • Loading branch information
arikfr committed Apr 1, 2015
2 parents 0bc7755 + 976d9ab commit fe41a70
Show file tree
Hide file tree
Showing 21 changed files with 331 additions and 104 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ upload:

test:
nosetests --with-coverage --cover-package=redash tests/*.py
cd rd_ui && grunt test
#cd rd_ui && grunt test
23 changes: 23 additions & 0 deletions migrations/0007_add_schedule_to_queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from playhouse.migrate import PostgresqlMigrator, migrate

from redash.models import db
from redash import models

if __name__ == '__main__':
db.connect_db()
migrator = PostgresqlMigrator(db.database)

with db.database.transaction():
migrate(
migrator.add_column('queries', 'schedule', models.Query.schedule),
)

db.database.execute_sql("UPDATE queries SET schedule = ttl WHERE ttl > 0;")

migrate(
migrator.drop_column('queries', 'ttl')
)

db.close_db(None)


14 changes: 7 additions & 7 deletions rd_ui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li class="active" ng-show="pageTitle"><a class="page-title" ng-bind="pageTitle"></a></li>
<li class="dropdown" ng-show="groupedDashboards.length > 0 || otherDashboards.length > 0 || currentUser.hasPermission('create_dashboard')">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-th-large"></span> <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown" ng-show="groupedDashboards.length > 0 || otherDashboards.length > 0 || currentUser.hasPermission('create_dashboard')" dropdown>
<a href="#" class="dropdown-toggle" dropdown-toggle><span class="glyphicon glyphicon-th-large"></span> <b class="caret"></b></a>
<ul class="dropdown-menu" dropdown-menu>
<span ng-repeat="(name, group) in groupedDashboards">
<li class="dropdown-submenu">
<a href="#" ng-bind="name"></a>
Expand All @@ -59,9 +59,9 @@
<li><a data-toggle="modal" href="#new_dashboard_dialog" ng-show="currentUser.hasPermission('create_dashboard')">New Dashboard</a></li>
</ul>
</li>
<li class="dropdown" ng-show="currentUser.hasPermission('view_query')">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Queries <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown" ng-show="currentUser.hasPermission('view_query')" dropdown>
<a href="#" class="dropdown-toggle" dropdown-toggle>Queries <b class="caret"></b></a>
<ul class="dropdown-menu" dropdown-menu>
<li ng-show="currentUser.hasPermission('create_query')"><a href="/queries/new">New Query</a></li>
<li><a href="/queries">Queries</a></li>
</ul>
Expand Down Expand Up @@ -123,7 +123,7 @@
<script src="/bower_components/marked/lib/marked.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>
<script src="/bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.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>
Expand Down
12 changes: 7 additions & 5 deletions rd_ui/app/scripts/controllers/controllers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
(function () {
var dateFormatter = function (value) {
if (!value) return "-";
if (!value) {
return "-";
}
return value.toDate().toLocaleString();
};

Expand Down Expand Up @@ -30,9 +32,9 @@
},
{
'label': 'Update Schedule',
'map': 'ttl',
'map': 'schedule',
'formatFunction': function (value) {
return $filter('refreshRateHumanize')(value);
return $filter('scheduleHumanize')(value);
}
}
];
Expand Down Expand Up @@ -127,9 +129,9 @@
},
{
'label': 'Update Schedule',
'map': 'ttl',
'map': 'schedule',
'formatFunction': function (value) {
return $filter('refreshRateHumanize')(value);
return $filter('scheduleHumanize')(value);
}
}
]
Expand Down
11 changes: 7 additions & 4 deletions rd_ui/app/scripts/controllers/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,13 @@
Events.record(currentUser, "autorefresh", "dashboard", dashboard.id, {'enable': $scope.refreshEnabled});

if ($scope.refreshEnabled) {
var refreshRate = _.min(_.flatten($scope.dashboard.widgets), function(widget) {
return widget.visualization.query.ttl;
}).visualization.query.ttl;
var refreshRate = _.min(_.map(_.flatten($scope.dashboard.widgets), function(widget) {
var schedule = widget.visualization.query.schedule;
if (schedule === null || schedule.match(/\d\d:\d\d/) !== null) {
return 60;
}
return widget.visualization.query.schedule;
}));

$scope.refreshRate = _.max([120, refreshRate * 2]) * 1000;

Expand Down Expand Up @@ -138,7 +142,6 @@
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';
} else {
Expand Down
2 changes: 1 addition & 1 deletion rd_ui/app/scripts/controllers/query_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
$scope.duplicateQuery = function() {
Events.record(currentUser, 'fork', 'query', $scope.query.id);
$scope.query.id = null;
$scope.query.ttl = -1;
$scope.query.schedule = null;

$scope.saveQuery({
successMessage: 'Query forked',
Expand Down
41 changes: 34 additions & 7 deletions rd_ui/app/scripts/controllers/query_view.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
(function() {
'use strict';

function QueryViewCtrl($scope, Events, $route, $location, notifications, growl, Query, DataSource) {
function QueryViewCtrl($scope, Events, $route, $location, notifications, growl, $modal, Query, DataSource) {
var DEFAULT_TAB = 'table';

var getQueryResult = function(ttl) {
var getQueryResult = function(maxAge) {
// 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'];
if (maxAge == undefined) {
maxAge = $location.search()['maxAge'];
}
$scope.queryResult = $scope.query.getQueryResult(ttl, parameters);

if (maxAge == undefined) {
maxAge = -1;
}

$scope.queryResult = $scope.query.getQueryResult(maxAge, parameters);
}

$scope.query = $route.current.locals.query;
Expand Down Expand Up @@ -98,7 +103,7 @@

return Query.delete({id: data.id}, function() {
$scope.query.is_archived = true;
$scope.query.ttl = -1;
$scope.query.schedule = null;
growl.addSuccessMessage(options.successMessage);
// This feels dirty.
$('#archive-confirmation-modal').modal('hide');
Expand Down Expand Up @@ -168,6 +173,28 @@
}
});

$scope.openScheduleForm = function() {
if (!$scope.isQueryOwner) {
return;
};

$modal.open({
templateUrl: '/views/schedule_form.html',
size: 'sm',
scope: $scope,
controller: function($scope, $modalInstance) {
$scope.close = function() {
$modalInstance.close();
}
if ($scope.query.hasDailySchedule()) {
$scope.refreshType = 'daily';
} else {
$scope.refreshType = 'periodic';
}
}
});
};

$scope.$watch(function() {
return $location.hash()
}, function(hash) {
Expand All @@ -180,5 +207,5 @@

angular.module('redash.controllers')
.controller('QueryViewCtrl',
['$scope', 'Events', '$route', '$location', 'notifications', 'growl', 'Query', 'DataSource', QueryViewCtrl]);
['$scope', 'Events', '$route', '$location', 'notifications', 'growl', '$modal', 'Query', 'DataSource', QueryViewCtrl]);
})();
72 changes: 61 additions & 11 deletions rd_ui/app/scripts/directives/query_directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,42 +111,91 @@
}
}

function queryTimePicker() {
return {
restrict: 'E',
template: '<select ng-disabled="refreshType != \'daily\'" ng-model="hour" ng-change="updateSchedule()" ng-options="c as c for c in hourOptions"></select> :\
<select ng-disabled="refreshType != \'daily\'" ng-model="minute" ng-change="updateSchedule()" ng-options="c as c for c in minuteOptions"></select>',
link: function($scope) {
var padWithZeros = function(size, v) {
v = String(v);
if (v.length < size) {
v = "0" + v;
}
return v;
};

$scope.hourOptions = _.map(_.range(0, 24), _.partial(padWithZeros, 2));
$scope.minuteOptions = _.map(_.range(0, 60, 5), _.partial(padWithZeros, 2));

if ($scope.query.hasDailySchedule()) {
var parts = $scope.query.scheduleInLocalTime().split(':');
$scope.minute = parts[1];
$scope.hour = parts[0];
} else {
$scope.minute = "15";
$scope.hour = "00";
}

$scope.updateSchedule = function() {
var newSchedule = moment($scope.hour + ":" + $scope.minute, 'HH:mm').utc().format('HH:mm');
if (newSchedule != $scope.query.schedule) {
$scope.query.schedule = newSchedule;
$scope.saveQuery();
}
};

$scope.$watch('refreshType', function() {
if ($scope.refreshType == 'daily') {
$scope.updateSchedule();
}
});
}
}
}

function queryRefreshSelect() {
return {
restrict: 'E',
template: '<select\
ng-disabled="!isQueryOwner"\
ng-model="query.ttl"\
ng-disabled="refreshType != \'periodic\'"\
ng-model="query.schedule"\
ng-change="saveQuery()"\
ng-options="c.value as c.name for c in refreshOptions">\
<option value="">No Refresh</option>\
</select>',
link: function($scope) {
$scope.refreshOptions = [
{
value: -1,
name: 'No Refresh'
},
{
value: 60,
value: "60",
name: 'Every minute'
},
}
]

_.each(_.range(1, 13), function (i) {
$scope.refreshOptions.push({
value: i * 3600,
value: String(i * 3600),
name: 'Every ' + i + 'h'
});
})

$scope.refreshOptions.push({
value: 24 * 3600,
value: String(24 * 3600),
name: 'Every 24h'
});
$scope.refreshOptions.push({
value: 7 * 24 * 3600,
value: String(7 * 24 * 3600),
name: 'Once a week'
});

$scope.$watch('refreshType', function() {
if ($scope.refreshType == 'periodic') {
if ($scope.query.hasDailySchedule()) {
$scope.query.schedule = null;
$scope.saveQuery();
}
}
});
}

}
Expand All @@ -158,5 +207,6 @@
.directive('queryResultLink', queryResultCSVLink)
.directive('queryEditor', queryEditor)
.directive('queryRefreshSelect', queryRefreshSelect)
.directive('queryTimePicker', queryTimePicker)
.directive('queryFormatter', ['$http', queryFormatter]);
})();
13 changes: 8 additions & 5 deletions rd_ui/app/scripts/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ angular.module('redash.filters', []).
return durationHumanize;
})

.filter('refreshRateHumanize', function () {
return function (ttl) {
if (ttl == -1) {
.filter('scheduleHumanize', function() {
return function (schedule) {
if (schedule === null) {
return "Never";
} else {
return "Every " + durationHumanize(ttl);
} else if (schedule.match(/\d\d:\d\d/) !== null) {
var localTime = moment.utc(schedule, 'HH:mm').local().format('HH:mm');
return "Every day at " + localTime;
}

return "Every " + durationHumanize(parseInt(schedule));
}
})

Expand Down
Loading

0 comments on commit fe41a70

Please sign in to comment.