mirror of
https://github.com/getredash/redash.git
synced 2025-12-26 21:01:31 -05:00
Compare commits
5 Commits
v0.3.5+b31
...
v0.3.5+b31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3852db164 | ||
|
|
b242295de0 | ||
|
|
a37142426c | ||
|
|
271d577074 | ||
|
|
2fd3033418 |
@@ -1,24 +1,25 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function QuerySourceCtrl($controller, $scope, $location, growl, Query, Visualization, KeyboardShortcuts) {
|
||||
function QuerySourceCtrl($controller, $scope, $location, Query, Visualization, KeyboardShortcuts) {
|
||||
// extends QueryViewCtrl
|
||||
$controller('QueryViewCtrl', {$scope: $scope});
|
||||
// TODO:
|
||||
// This doesn't get inherited. Setting it on this didn't work either (which is weird).
|
||||
// Obviously it shouldn't be repeated, but we got bigger fish to fry.
|
||||
var DEFAULT_TAB = 'table';
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$scope.sourceMode = true;
|
||||
$scope.canEdit = currentUser.canEdit($scope.query);
|
||||
@@ -90,11 +91,10 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('redash.controllers').controller('QuerySourceCtrl', [
|
||||
'$controller', '$scope', '$location', 'growl', 'Query',
|
||||
'$controller', '$scope', '$location', 'Query',
|
||||
'Visualization', 'KeyboardShortcuts', QuerySourceCtrl
|
||||
]);
|
||||
})();
|
||||
@@ -1,279 +1,290 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
Highcharts.setOptions({
|
||||
colors: ["#4572A7", "#AA4643", "#89A54E", "#80699B", "#3D96AE",
|
||||
"#DB843D", "#92A8CD", "#A47D7C", "#B5CA92"]
|
||||
});
|
||||
Highcharts.setOptions({
|
||||
colors: ["#4572A7", "#AA4643", "#89A54E", "#80699B", "#3D96AE",
|
||||
"#DB843D", "#92A8CD", "#A47D7C", "#B5CA92"]
|
||||
});
|
||||
|
||||
var defaultOptions = {
|
||||
title: {
|
||||
"text": null
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null
|
||||
var defaultOptions = {
|
||||
title: {
|
||||
"text": null
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
valueDecimals: 2,
|
||||
formatter: function () {
|
||||
if (!this.points) {
|
||||
this.points = [this.point];
|
||||
}
|
||||
;
|
||||
|
||||
if (moment.isMoment(this.x)) {
|
||||
var s = '<b>' + moment(this.x).format("DD/MM/YY HH:mm") + '</b>',
|
||||
pointsCount = this.points.length;
|
||||
|
||||
$.each(this.points, function (i, point) {
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' +
|
||||
Highcharts.numberFormat(point.y);
|
||||
|
||||
if (pointsCount > 1 && point.percentage) {
|
||||
s += " (" + Highcharts.numberFormat(point.percentage) + "%)";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var points = this.points;
|
||||
var name = points[0].key || points[0].name;
|
||||
|
||||
var s = "<b>" + name + "</b>";
|
||||
|
||||
$.each(points, function (i, point) {
|
||||
if (points.length > 1) {
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' + Highcharts.numberFormat(point.y);
|
||||
} else {
|
||||
s += ": " + Highcharts.numberFormat(point.y);
|
||||
if (point.percentage < 100) {
|
||||
s += ' (' + Highcharts.numberFormat(point.percentage) + '%)';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return s;
|
||||
},
|
||||
shared: true
|
||||
},
|
||||
exporting: {
|
||||
chartOptions: {
|
||||
title: {
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
buttons: {
|
||||
contextButton: {
|
||||
menuItems: [
|
||||
{
|
||||
text: 'Toggle % Stacking',
|
||||
onclick: function () {
|
||||
var newStacking = "normal";
|
||||
if (this.series[0].options.stacking == "normal") {
|
||||
newStacking = "percent";
|
||||
}
|
||||
|
||||
_.each(this.series, function (series) {
|
||||
series.update({stacking: newStacking}, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
area: {
|
||||
marker: {
|
||||
enabled: false,
|
||||
symbol: 'circle',
|
||||
radius: 2,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
column: {
|
||||
stacking: "normal",
|
||||
pointPadding: 0,
|
||||
borderWidth: 1,
|
||||
groupPadding: 0,
|
||||
shadow: false
|
||||
},
|
||||
line: {
|
||||
marker: {
|
||||
radius: 1
|
||||
},
|
||||
lineWidth: 2,
|
||||
states: {
|
||||
hover: {
|
||||
lineWidth: 2,
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
format: '<b>{point.name}</b>: {point.y} ({point.percentage:.1f} %)'
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
marker: {
|
||||
radius: 5,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true,
|
||||
lineColor: 'rgb(100,100,100)'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
valueDecimals: 2,
|
||||
formatter: function () {
|
||||
if (!this.points) {
|
||||
this.points = [this.point];
|
||||
};
|
||||
headerFormat: '<b>{series.name}</b><br>',
|
||||
pointFormat: '{point.x}, {point.y}'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
if (moment.isMoment(this.x)) {
|
||||
var s = '<b>' + moment(this.x).format("DD/MM/YY HH:mm") + '</b>',
|
||||
pointsCount = this.points.length;
|
||||
angular.module('highchart', [])
|
||||
.directive('chart', ['$timeout', function ($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<div></div>',
|
||||
scope: {
|
||||
options: "=options",
|
||||
series: "=series"
|
||||
},
|
||||
transclude: true,
|
||||
replace: true,
|
||||
|
||||
$.each(this.points, function (i, point) {
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' +
|
||||
Highcharts.numberFormat(point.y);
|
||||
|
||||
if (pointsCount > 1 && point.percentage) {
|
||||
s += " (" + Highcharts.numberFormat(point.percentage) + "%)";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var points = this.points;
|
||||
var name = points[0].key || points[0].name;
|
||||
|
||||
var s = "<b>" + name + "</b>";
|
||||
|
||||
$.each(points, function (i, point) {
|
||||
if (points.length > 1) {
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' + Highcharts.numberFormat(point.y);
|
||||
} else {
|
||||
s += ": " + Highcharts.numberFormat(point.y);
|
||||
if (point.percentage < 100) {
|
||||
s += ' (' +Highcharts.numberFormat(point.percentage) + '%)';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return s;
|
||||
},
|
||||
shared: true
|
||||
},
|
||||
exporting: {
|
||||
chartOptions: {
|
||||
title: {
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
buttons: {
|
||||
contextButton: {
|
||||
menuItems: [
|
||||
{
|
||||
text: 'Toggle % Stacking',
|
||||
onclick: function () {
|
||||
var newStacking = "normal";
|
||||
if (this.series[0].options.stacking == "normal") {
|
||||
newStacking = "percent";
|
||||
}
|
||||
|
||||
_.each(this.series, function (series) {
|
||||
series.update({stacking: newStacking}, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
credits: {
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
area: {
|
||||
marker: {
|
||||
enabled: false,
|
||||
symbol: 'circle',
|
||||
radius: 2,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
column: {
|
||||
stacking: "normal",
|
||||
pointPadding: 0,
|
||||
borderWidth: 1,
|
||||
groupPadding: 0,
|
||||
shadow: false
|
||||
},
|
||||
line: {
|
||||
marker: {
|
||||
radius: 1
|
||||
},
|
||||
lineWidth: 2,
|
||||
states: {
|
||||
hover: {
|
||||
lineWidth: 2,
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
format: '<b>{point.name}</b>: {point.y} ({point.percentage:.1f} %)'
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
marker: {
|
||||
radius: 5,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true,
|
||||
lineColor: 'rgb(100,100,100)'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
headerFormat: '<b>{series.name}</b><br>',
|
||||
pointFormat: '{point.x}, {point.y}'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
angular.module('highchart', [])
|
||||
.directive('chart', ['$timeout', function ($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<div></div>',
|
||||
scope: {
|
||||
options: "=options",
|
||||
series: "=series"
|
||||
},
|
||||
transclude: true,
|
||||
replace: true,
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
var chartsDefaults = {
|
||||
chart: {
|
||||
renderTo: element[0],
|
||||
type: attrs.type || null,
|
||||
height: attrs.height || null,
|
||||
width: attrs.width || null
|
||||
}
|
||||
};
|
||||
|
||||
var chartOptions = $.extend(true, {}, defaultOptions, chartsDefaults);
|
||||
|
||||
// $timeout makes sure that this function invoked after the DOM ready. When draw/init
|
||||
// invoked after the DOM is ready, we see first an empty HighCharts objects and later
|
||||
// they get filled up. Which gives the feeling that the charts loading faster (otherwise
|
||||
// we stare at an empty screen until the HighCharts object is ready).
|
||||
$timeout(function(){
|
||||
// Update when options change
|
||||
scope.$watch('options', function (newOptions) {
|
||||
initChart(newOptions);
|
||||
}, true);
|
||||
|
||||
//Update when charts data changes
|
||||
scope.$watchCollection('series', function (series) {
|
||||
if (!series || series.length == 0) {
|
||||
scope.chart.showLoading();
|
||||
} else {
|
||||
drawChart();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function initChart(options) {
|
||||
if (scope.chart) {
|
||||
scope.chart.destroy();
|
||||
};
|
||||
|
||||
$.extend(true, chartOptions, options);
|
||||
|
||||
scope.chart = new Highcharts.Chart(chartOptions);
|
||||
drawChart();
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
while (scope.chart.series.length > 0) {
|
||||
scope.chart.series[0].remove(false);
|
||||
};
|
||||
|
||||
if (scope.series.length > 0 && _.some(scope.series[0].data, function (p) {
|
||||
return (angular.isString(p.x) || angular.isDefined(p.name));
|
||||
})) {
|
||||
scope.chart.xAxis[0].update({type: 'category'});
|
||||
|
||||
if (!angular.isDefined(scope.series[0].data[0].name)) {
|
||||
// We need to make sure that for each category, each series has a value.
|
||||
var categories = _.union.apply(this, _.map(scope.series, function (s) {
|
||||
return _.pluck(s.data, 'x')
|
||||
}));
|
||||
|
||||
_.each(scope.series, function (s) {
|
||||
// TODO: move this logic to Query#getChartData
|
||||
var yValues = _.groupBy(s.data, 'x');
|
||||
|
||||
var newData = _.map(categories, function (category) {
|
||||
return {
|
||||
name: category,
|
||||
y: (yValues[category] && yValues[category][0].y) || 0
|
||||
}
|
||||
});
|
||||
|
||||
if (categories.length == 1) {
|
||||
newData = _.sortBy(newData, 'y').reverse();
|
||||
};
|
||||
|
||||
s.data = newData;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
scope.chart.xAxis[0].update({type: 'datetime'});
|
||||
}
|
||||
|
||||
scope.chart.counters.color = 0;
|
||||
|
||||
_.each(scope.series, function (s) {
|
||||
// here we override the series with the visualization config
|
||||
s = _.extend(s, chartOptions['series']);
|
||||
|
||||
if (s.type == 'area') {
|
||||
_.each(s.data, function (p) {
|
||||
// This is an insane hack: somewhere deep in HighChart's code,
|
||||
// when you stack areas, it tries to convert the string representation
|
||||
// of point's x into a number. With the default implementation of toString
|
||||
// it fails....
|
||||
|
||||
if (moment.isMoment(p.x)) {
|
||||
p.x.toString = function () {
|
||||
return String(this.toDate().getTime());
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scope.chart.addSeries(s, false);
|
||||
});
|
||||
|
||||
scope.chart.redraw();
|
||||
scope.chart.hideLoading();
|
||||
}
|
||||
|
||||
}
|
||||
link: function (scope, element, attrs) {
|
||||
var chartsDefaults = {
|
||||
chart: {
|
||||
renderTo: element[0],
|
||||
type: attrs.type || null,
|
||||
height: attrs.height || null,
|
||||
width: attrs.width || null
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
var chartOptions = $.extend(true, {}, defaultOptions, chartsDefaults);
|
||||
|
||||
// $timeout makes sure that this function invoked after the DOM ready. When draw/init
|
||||
// invoked after the DOM is ready, we see first an empty HighCharts objects and later
|
||||
// they get filled up. Which gives the feeling that the charts loading faster (otherwise
|
||||
// we stare at an empty screen until the HighCharts object is ready).
|
||||
$timeout(function () {
|
||||
// Update when options change
|
||||
scope.$watch('options', function (newOptions) {
|
||||
initChart(newOptions);
|
||||
}, true);
|
||||
|
||||
//Update when charts data changes
|
||||
scope.$watchCollection('series', function (series) {
|
||||
if (!series || series.length == 0) {
|
||||
scope.chart.showLoading();
|
||||
} else {
|
||||
drawChart();
|
||||
}
|
||||
;
|
||||
});
|
||||
});
|
||||
|
||||
function initChart(options) {
|
||||
if (scope.chart) {
|
||||
scope.chart.destroy();
|
||||
}
|
||||
;
|
||||
|
||||
$.extend(true, chartOptions, options);
|
||||
|
||||
scope.chart = new Highcharts.Chart(chartOptions);
|
||||
drawChart();
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
while (scope.chart.series.length > 0) {
|
||||
scope.chart.series[0].remove(false);
|
||||
};
|
||||
|
||||
if (!('xAxis' in chartOptions && 'type' in chartOptions['xAxis'])) {
|
||||
if (scope.series.length > 0 && _.some(scope.series[0].data, function (p) {
|
||||
return (angular.isString(p.x) || angular.isDefined(p.name));
|
||||
})) {
|
||||
chartOptions['xAxis'] = chartOptions['xAxis'] || {};
|
||||
chartOptions['xAxis']['type'] = 'category';
|
||||
} else {
|
||||
chartOptions['xAxis'] = chartOptions['xAxis'] || {};
|
||||
chartOptions['xAxis']['type'] = 'datetime';
|
||||
}
|
||||
}
|
||||
|
||||
if (chartOptions['xAxis']['type'] == 'category') {
|
||||
if (!angular.isDefined(scope.series[0].data[0].name)) {
|
||||
// We need to make sure that for each category, each series has a value.
|
||||
var categories = _.union.apply(this, _.map(scope.series, function (s) {
|
||||
return _.pluck(s.data, 'x')
|
||||
}));
|
||||
|
||||
_.each(scope.series, function (s) {
|
||||
// TODO: move this logic to Query#getChartData
|
||||
var yValues = _.groupBy(s.data, 'x');
|
||||
|
||||
var newData = _.map(categories, function (category) {
|
||||
return {
|
||||
name: category,
|
||||
y: (yValues[category] && yValues[category][0].y) || 0
|
||||
}
|
||||
});
|
||||
|
||||
if (categories.length == 1) {
|
||||
newData = _.sortBy(newData, 'y').reverse();
|
||||
}
|
||||
;
|
||||
|
||||
s.data = newData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope.chart.counters.color = 0;
|
||||
|
||||
_.each(scope.series, function (s) {
|
||||
// here we override the series with the visualization config
|
||||
s = _.extend(s, chartOptions['series']);
|
||||
|
||||
if (s.type == 'area') {
|
||||
_.each(s.data, function (p) {
|
||||
// This is an insane hack: somewhere deep in HighChart's code,
|
||||
// when you stack areas, it tries to convert the string representation
|
||||
// of point's x into a number. With the default implementation of toString
|
||||
// it fails....
|
||||
|
||||
if (moment.isMoment(p.x)) {
|
||||
p.x.toString = function () {
|
||||
return String(this.toDate().getTime());
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
;
|
||||
|
||||
scope.chart.addSeries(s, false);
|
||||
});
|
||||
|
||||
scope.chart.redraw();
|
||||
scope.chart.hideLoading();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
})();
|
||||
@@ -1,106 +1,123 @@
|
||||
(function () {
|
||||
var chartVisualization = angular.module('redash.visualization');
|
||||
var chartVisualization = angular.module('redash.visualization');
|
||||
|
||||
chartVisualization.config(['VisualizationProvider', function(VisualizationProvider) {
|
||||
var renderTemplate = '<chart-renderer options="visualization.options" query-result="queryResult"></chart-renderer>';
|
||||
var editTemplate = '<chart-editor></chart-editor>';
|
||||
var defaultOptions = {
|
||||
'series': {
|
||||
'type': 'column',
|
||||
'stacking': null
|
||||
}
|
||||
chartVisualization.config(['VisualizationProvider', function (VisualizationProvider) {
|
||||
var renderTemplate = '<chart-renderer options="visualization.options" query-result="queryResult"></chart-renderer>';
|
||||
var editTemplate = '<chart-editor></chart-editor>';
|
||||
var defaultOptions = {
|
||||
'series': {
|
||||
'type': 'column',
|
||||
'stacking': null
|
||||
}
|
||||
};
|
||||
|
||||
VisualizationProvider.registerVisualization({
|
||||
type: 'CHART',
|
||||
name: 'Chart',
|
||||
renderTemplate: renderTemplate,
|
||||
editorTemplate: editTemplate,
|
||||
defaultOptions: defaultOptions
|
||||
});
|
||||
}]);
|
||||
|
||||
chartVisualization.directive('chartRenderer', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
queryResult: '=',
|
||||
options: '=?'
|
||||
},
|
||||
template: "<chart options='chartOptions' series='chartSeries' class='graph'></chart>",
|
||||
replace: false,
|
||||
controller: ['$scope', function ($scope) {
|
||||
$scope.chartSeries = [];
|
||||
$scope.chartOptions = {};
|
||||
|
||||
$scope.$watch('options', function (chartOptions) {
|
||||
if (chartOptions) {
|
||||
$scope.chartOptions = chartOptions;
|
||||
}
|
||||
});
|
||||
$scope.$watch('queryResult && queryResult.getData()', function (data) {
|
||||
if (!data || $scope.queryResult.getData() == null) {
|
||||
$scope.chartSeries.splice(0, $scope.chartSeries.length);
|
||||
} else {
|
||||
$scope.chartSeries.splice(0, $scope.chartSeries.length);
|
||||
|
||||
_.each($scope.queryResult.getChartData(), function (s) {
|
||||
$scope.chartSeries.push(_.extend(s, {'stacking': 'normal'}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
chartVisualization.directive('chartEditor', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/views/visualizations/chart_editor.html',
|
||||
link: function (scope, element, attrs) {
|
||||
scope.seriesTypes = {
|
||||
'Line': 'line',
|
||||
'Column': 'column',
|
||||
'Area': 'area',
|
||||
'Scatter': 'scatter',
|
||||
'Pie': 'pie'
|
||||
};
|
||||
|
||||
VisualizationProvider.registerVisualization({
|
||||
type: 'CHART',
|
||||
name: 'Chart',
|
||||
renderTemplate: renderTemplate,
|
||||
editorTemplate: editTemplate,
|
||||
defaultOptions: defaultOptions
|
||||
});
|
||||
}]);
|
||||
scope.stackingOptions = {
|
||||
"None": "none",
|
||||
"Normal": "normal",
|
||||
"Percent": "percent"
|
||||
};
|
||||
|
||||
chartVisualization.directive('chartRenderer', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
queryResult: '=',
|
||||
options: '=?'
|
||||
},
|
||||
template: "<chart options='chartOptions' series='chartSeries' class='graph'></chart>",
|
||||
replace: false,
|
||||
controller: ['$scope', function ($scope) {
|
||||
$scope.chartSeries = [];
|
||||
$scope.chartOptions = {};
|
||||
scope.xAxisOptions = {
|
||||
"Date/Time": "datetime",
|
||||
"Linear": "linear",
|
||||
"Category": "category"
|
||||
};
|
||||
|
||||
$scope.$watch('options', function(chartOptions) {
|
||||
if (chartOptions) {
|
||||
$scope.chartOptions = chartOptions;
|
||||
}
|
||||
});
|
||||
$scope.$watch('queryResult && queryResult.getData()', function (data) {
|
||||
if (!data || $scope.queryResult.getData() == null) {
|
||||
$scope.chartSeries.splice(0, $scope.chartSeries.length);
|
||||
} else {
|
||||
$scope.chartSeries.splice(0, $scope.chartSeries.length);
|
||||
scope.xAxisType = "datetime";
|
||||
scope.stacking = "none";
|
||||
|
||||
_.each($scope.queryResult.getChartData(), function (s) {
|
||||
$scope.chartSeries.push(_.extend(s, {'stacking': 'normal'}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}]
|
||||
}
|
||||
});
|
||||
var chartOptionsUnwatch = null;
|
||||
|
||||
chartVisualization.directive('chartEditor', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/views/visualizations/chart_editor.html',
|
||||
link: function (scope, element, attrs) {
|
||||
scope.seriesTypes = {
|
||||
'Line': 'line',
|
||||
'Column': 'column',
|
||||
'Area': 'area',
|
||||
'Scatter': 'scatter',
|
||||
'Pie': 'pie'
|
||||
};
|
||||
|
||||
scope.stackingOptions = {
|
||||
"None": "none",
|
||||
"Normal": "normal",
|
||||
"Percent": "percent"
|
||||
};
|
||||
|
||||
scope.stacking = "none";
|
||||
|
||||
var chartOptionsUnwatch = null;
|
||||
|
||||
scope.$watch('visualization', function (visualization) {
|
||||
if (visualization && visualization.type == 'CHART') {
|
||||
if (scope.visualization.options.series.stacking === null) {
|
||||
scope.stacking = "none";
|
||||
} else if (scope.visualization.options.series.stacking === undefined) {
|
||||
scope.stacking = "normal";
|
||||
} else {
|
||||
scope.stacking = scope.visualization.options.series.stacking;
|
||||
}
|
||||
|
||||
chartOptionsUnwatch = scope.$watch("stacking", function (stacking) {
|
||||
if (stacking == "none") {
|
||||
scope.visualization.options.series.stacking = null;
|
||||
} else {
|
||||
scope.visualization.options.series.stacking = stacking;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (chartOptionsUnwatch) {
|
||||
chartOptionsUnwatch();
|
||||
chartOptionsUnwatch = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
scope.$watch('visualization', function (visualization) {
|
||||
if (visualization && visualization.type == 'CHART') {
|
||||
if (scope.visualization.options.series.stacking === null) {
|
||||
scope.stacking = "none";
|
||||
} else if (scope.visualization.options.series.stacking === undefined) {
|
||||
scope.stacking = "normal";
|
||||
} else {
|
||||
scope.stacking = scope.visualization.options.series.stacking;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chartOptionsUnwatch = scope.$watch("stacking", function (stacking) {
|
||||
if (stacking == "none") {
|
||||
scope.visualization.options.series.stacking = null;
|
||||
} else {
|
||||
scope.visualization.options.series.stacking = stacking;
|
||||
}
|
||||
});
|
||||
|
||||
xAxisUnwatch = scope.$watch("xAxisType", function (xAxisType) {
|
||||
scope.visualization.options.xAxis = scope.visualization.options.xAxis || {};
|
||||
scope.visualization.options.xAxis.type = xAxisType;
|
||||
});
|
||||
} else {
|
||||
if (chartOptionsUnwatch) {
|
||||
chartOptionsUnwatch();
|
||||
chartOptionsUnwatch = null;
|
||||
}
|
||||
|
||||
if (xAxisUnwatch) {
|
||||
xAxisUnwatch();
|
||||
xAxisUnwatch = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}());
|
||||
@@ -7,5 +7,8 @@
|
||||
<div class="form-group">
|
||||
<label class="control-label">Stacking</label>
|
||||
<select required ng-model="stacking" ng-options="value as key for (key, value) in stackingOptions" class="form-control"></select>
|
||||
|
||||
<label class="control-label">X Axis Type</label>
|
||||
<select required ng-model="xAxisType" ng-options="value as key for (key, value) in xAxisOptions" class="form-control"></select>
|
||||
</div>
|
||||
</div>
|
||||
10
redash/data/manager.py
Normal file → Executable file
10
redash/data/manager.py
Normal file → Executable file
@@ -6,17 +6,25 @@ import logging
|
||||
import peewee
|
||||
import qr
|
||||
import redis
|
||||
import json
|
||||
from redash import models
|
||||
from redash.data import worker
|
||||
from redash.utils import gen_query_hash
|
||||
|
||||
|
||||
class JSONPriorityQueue(qr.PriorityQueue):
|
||||
""" Use a JSON serializer to help with cross language support """
|
||||
def __init__(self, key, **kwargs):
|
||||
super(qr.PriorityQueue, self).__init__(key, **kwargs)
|
||||
self.serializer = json
|
||||
|
||||
|
||||
class Manager(object):
|
||||
def __init__(self, redis_connection, statsd_client):
|
||||
self.statsd_client = statsd_client
|
||||
self.redis_connection = redis_connection
|
||||
self.workers = []
|
||||
self.queue = qr.PriorityQueue("jobs", **self.redis_connection.connection_pool.connection_kwargs)
|
||||
self.queue = JSONPriorityQueue("jobs", **self.redis_connection.connection_pool.connection_kwargs)
|
||||
self.max_retries = 5
|
||||
self.status = {
|
||||
'last_refresh_at': 0,
|
||||
|
||||
Reference in New Issue
Block a user