mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
merge with master
This commit is contained in:
@@ -41,6 +41,7 @@ import 'app/core/routes/routes';
|
||||
import './filters/filters';
|
||||
import coreModule from './core_module';
|
||||
import appEvents from './app_events';
|
||||
import colors from './utils/colors';
|
||||
|
||||
|
||||
export {
|
||||
@@ -60,4 +61,5 @@ export {
|
||||
dashboardSelector,
|
||||
queryPartEditorDirective,
|
||||
WizardFlow,
|
||||
colors,
|
||||
};
|
||||
|
||||
@@ -23,10 +23,10 @@ function (_, $, coreModule) {
|
||||
getOptions: "&",
|
||||
onChange: "&",
|
||||
},
|
||||
link: function($scope, elem, attrs) {
|
||||
link: function($scope, elem) {
|
||||
var $input = $(inputTemplate);
|
||||
var $button = $(attrs.styleMode === 'select' ? selectTemplate : linkTemplate);
|
||||
var segment = $scope.segment;
|
||||
var $button = $(segment.selectMode ? selectTemplate : linkTemplate);
|
||||
var options = null;
|
||||
var cancelBlur = null;
|
||||
var linkMode = true;
|
||||
@@ -170,6 +170,7 @@ function (_, $, coreModule) {
|
||||
},
|
||||
link: {
|
||||
pre: function postLink($scope, elem, attrs) {
|
||||
var cachedOptions;
|
||||
|
||||
$scope.valueToSegment = function(value) {
|
||||
var option = _.find($scope.options, {value: value});
|
||||
@@ -177,7 +178,9 @@ function (_, $, coreModule) {
|
||||
cssClass: attrs.cssClass,
|
||||
custom: attrs.custom,
|
||||
value: option ? option.text : value,
|
||||
selectMode: attrs.selectMode,
|
||||
};
|
||||
|
||||
return uiSegmentSrv.newSegment(segment);
|
||||
};
|
||||
|
||||
@@ -188,13 +191,20 @@ function (_, $, coreModule) {
|
||||
});
|
||||
return $q.when(optionSegments);
|
||||
} else {
|
||||
return $scope.getOptions();
|
||||
return $scope.getOptions().then(function(options) {
|
||||
cachedOptions = options;
|
||||
return _.map(options, function(option) {
|
||||
return uiSegmentSrv.newSegment({value: option.text});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onSegmentChange = function() {
|
||||
if ($scope.options) {
|
||||
var option = _.find($scope.options, {text: $scope.segment.value});
|
||||
var options = $scope.options || cachedOptions;
|
||||
|
||||
if (options) {
|
||||
var option = _.find(options, {text: $scope.segment.value});
|
||||
if (option && option.value !== $scope.property) {
|
||||
$scope.property = option.value;
|
||||
} else if (attrs.custom !== 'false') {
|
||||
|
||||
@@ -114,6 +114,10 @@ export class BackendSrv {
|
||||
var requestIsLocal = options.url.indexOf('/') === 0;
|
||||
var firstAttempt = options.retry === 0;
|
||||
|
||||
if (requestIsLocal && !options.hasSubUrl && options.retry === 0) {
|
||||
options.url = config.appSubUrl + options.url;
|
||||
}
|
||||
|
||||
if (requestIsLocal && options.headers && options.headers.Authorization) {
|
||||
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
||||
delete options.headers.Authorization;
|
||||
|
||||
@@ -28,6 +28,7 @@ function (angular, _, coreModule) {
|
||||
this.type = options.type;
|
||||
this.fake = options.fake;
|
||||
this.value = options.value;
|
||||
this.selectMode = options.selectMode;
|
||||
this.type = options.type;
|
||||
this.expandable = options.expandable;
|
||||
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
|
||||
@@ -31,6 +31,8 @@ export default class TimeSeries {
|
||||
allIsZero: boolean;
|
||||
decimals: number;
|
||||
scaledDecimals: number;
|
||||
hasMsResolution: boolean;
|
||||
isOutsideRange: boolean;
|
||||
|
||||
lines: any;
|
||||
bars: any;
|
||||
@@ -54,6 +56,7 @@ export default class TimeSeries {
|
||||
this.stats = {};
|
||||
this.legend = true;
|
||||
this.unit = opts.unit;
|
||||
this.hasMsResolution = this.isMsResolutionNeeded();
|
||||
}
|
||||
|
||||
applySeriesOverrides(overrides) {
|
||||
|
||||
12
public/app/core/utils/colors.ts
Normal file
12
public/app/core/utils/colors.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export default [
|
||||
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0",
|
||||
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477",
|
||||
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0",
|
||||
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93",
|
||||
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7",
|
||||
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B",
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"
|
||||
];
|
||||
|
||||
@@ -174,7 +174,10 @@ function($, _, moment) {
|
||||
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
|
||||
}
|
||||
else {
|
||||
return userInterval;
|
||||
return {
|
||||
intervalMs: kbn.interval_to_ms(userInterval),
|
||||
interval: userInterval,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +186,10 @@ function($, _, moment) {
|
||||
intervalMs = lowLimitMs;
|
||||
}
|
||||
|
||||
return kbn.secondsToHms(intervalMs / 1000);
|
||||
return {
|
||||
intervalMs: intervalMs,
|
||||
interval: kbn.secondsToHms(intervalMs / 1000),
|
||||
};
|
||||
};
|
||||
|
||||
kbn.describe_interval = function (string) {
|
||||
|
||||
@@ -227,8 +227,8 @@ export class AlertTabCtrl {
|
||||
|
||||
var datasourceName = foundTarget.datasource || this.panel.datasource;
|
||||
this.datasourceSrv.get(datasourceName).then(ds => {
|
||||
if (ds.meta.id !== 'graphite') {
|
||||
this.error = 'Currently the alerting backend only supports Graphite queries';
|
||||
if (!ds.meta.alerting) {
|
||||
this.error = 'The datasource does not support alerting queries';
|
||||
} else if (this.templateSrv.variableExists(foundTarget.target)) {
|
||||
this.error = 'Template variables are not supported in alert queries';
|
||||
} else {
|
||||
|
||||
@@ -30,6 +30,7 @@ export class DashboardModel {
|
||||
snapshot: any;
|
||||
schemaVersion: number;
|
||||
version: number;
|
||||
revision: number;
|
||||
links: any;
|
||||
gnetId: any;
|
||||
meta: any;
|
||||
@@ -42,6 +43,7 @@ export class DashboardModel {
|
||||
|
||||
this.events = new Emitter();
|
||||
this.id = data.id || null;
|
||||
this.revision = data.revision;
|
||||
this.title = data.title || 'No Title';
|
||||
this.autoUpdate = data.autoUpdate;
|
||||
this.description = data.description;
|
||||
|
||||
@@ -8,7 +8,7 @@ function (angular, _, require, config) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('ShareModalCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, $element, templateSrv, linkSrv) {
|
||||
module.controller('ShareModalCtrl', function($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {
|
||||
|
||||
$scope.options = { forCurrent: true, includeTemplateVars: true, theme: 'current' };
|
||||
$scope.editor = { index: $scope.tabIndex || 0};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div ng-repeat="variable in ctrl.variables" ng-hide="variable.hide === 2" class="submenu-item gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label template-variable" ng-hide="variable.hide === 1">
|
||||
{{variable.label || variable.name}}:
|
||||
{{variable.label || variable.name}}
|
||||
</label>
|
||||
<value-select-dropdown ng-if="variable.type !== 'adhoc'" variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ var template = `
|
||||
Panel data source
|
||||
</label>
|
||||
|
||||
<metric-segment segment="ctrl.dsSegment" style-mode="select"
|
||||
<metric-segment segment="ctrl.dsSegment"
|
||||
get-options="ctrl.getOptions()"
|
||||
on-change="ctrl.datasourceChanged()"></metric-segment>
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@ export class MetricsDsSelectorCtrl {
|
||||
this.current = {name: dsValue + ' not found', value: null};
|
||||
}
|
||||
|
||||
this.dsSegment = uiSegmentSrv.newSegment(this.current.name);
|
||||
this.dsSegment = uiSegmentSrv.newSegment({value: this.current.name, selectMode: true});
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
|
||||
@@ -25,6 +25,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
range: any;
|
||||
rangeRaw: any;
|
||||
interval: any;
|
||||
intervalMs: any;
|
||||
resolution: any;
|
||||
timeInfo: any;
|
||||
skipDataOnInit: boolean;
|
||||
@@ -123,11 +124,22 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
|
||||
}
|
||||
|
||||
var panelInterval = this.panel.interval;
|
||||
var datasourceInterval = (this.datasource || {}).interval;
|
||||
this.interval = kbn.calculateInterval(this.range, this.resolution, panelInterval || datasourceInterval);
|
||||
this.calculateInterval();
|
||||
};
|
||||
|
||||
calculateInterval() {
|
||||
var intervalOverride = this.panel.interval;
|
||||
|
||||
// if no panel interval check datasource
|
||||
if (!intervalOverride && this.datasource && this.datasource.interval) {
|
||||
intervalOverride = this.datasource.interval;
|
||||
}
|
||||
|
||||
var res = kbn.calculateInterval(this.range, this.resolution, intervalOverride);
|
||||
this.interval = res.interval;
|
||||
this.intervalMs = res.intervalMs;
|
||||
}
|
||||
|
||||
applyPanelTimeOverrides() {
|
||||
this.timeInfo = '';
|
||||
|
||||
@@ -183,6 +195,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
range: this.range,
|
||||
rangeRaw: this.rangeRaw,
|
||||
interval: this.interval,
|
||||
intervalMs: this.intervalMs,
|
||||
targets: this.panel.targets,
|
||||
format: this.panel.renderer === 'png' ? 'png' : 'json',
|
||||
maxDataPoints: this.resolution,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-lg-6">
|
||||
<div class="playlist-search-containerwrapper">
|
||||
<div class="max-width-32">
|
||||
<h5 class="page-headering playlist-column-header">Available</h5>
|
||||
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="col-lg-6">
|
||||
<h5 class="page headering playlist-column-header">Selected</h5>
|
||||
<table class="grafana-options-table playlist-available-list">
|
||||
<tr ng-repeat="playlistItem in ctrl.playlistItems">
|
||||
|
||||
@@ -14,7 +14,7 @@ export class PlaylistSearchCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $scope, private $location, private $timeout, private backendSrv, private contextSrv) {
|
||||
this.query = { query: '', tag: [], starred: false };
|
||||
this.query = {query: '', tag: [], starred: false, limit: 30};
|
||||
|
||||
$timeout(() => {
|
||||
this.query.query = '';
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<h1>Plugins</h1>
|
||||
<h1>
|
||||
Plugins <span class="muted small">(currently installed)</span>
|
||||
</h1>
|
||||
|
||||
<div class="page-header-tabs">
|
||||
<ul class="gf-tabs">
|
||||
@@ -25,7 +27,7 @@
|
||||
</ul>
|
||||
|
||||
<a class="get-more-plugins-link" href="https://grafana.net/plugins?utm_source=grafana_plugin_list" target="_blank">
|
||||
Find plugins on
|
||||
Find more plugins on
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export class ConstantVariable implements Variable {
|
||||
current: {},
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
/** @ngInject **/
|
||||
constructor(private model, private variableSrv) {
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export class DatasourceVariable implements Variable {
|
||||
query: string;
|
||||
options: any;
|
||||
current: any;
|
||||
refresh: any;
|
||||
|
||||
defaults = {
|
||||
type: 'datasource',
|
||||
@@ -20,11 +21,13 @@ export class DatasourceVariable implements Variable {
|
||||
regex: '',
|
||||
options: [],
|
||||
query: '',
|
||||
refresh: 1,
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
/** @ngInject **/
|
||||
constructor(private model, private datasourceSrv, private variableSrv) {
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
this.refresh = 1;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {variableTypes} from './variable';
|
||||
|
||||
export class VariableEditorCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
/** @ngInject **/
|
||||
constructor(private $scope, private datasourceSrv, private variableSrv, templateSrv) {
|
||||
$scope.variableTypes = variableTypes;
|
||||
$scope.ctrl = {};
|
||||
|
||||
@@ -28,7 +28,7 @@ export class IntervalVariable implements Variable {
|
||||
auto_count: 30,
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
/** @ngInject **/
|
||||
constructor(private model, private timeSrv, private templateSrv, private variableSrv) {
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
this.refresh = 2;
|
||||
@@ -54,8 +54,8 @@ export class IntervalVariable implements Variable {
|
||||
this.options.unshift({ text: 'auto', value: '$__auto_interval' });
|
||||
}
|
||||
|
||||
var interval = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null));
|
||||
this.templateSrv.setGrafanaVariable('$__auto_interval', interval);
|
||||
var res = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, (this.auto_min ? ">"+this.auto_min : null));
|
||||
this.templateSrv.setGrafanaVariable('$__auto_interval', res.interval);
|
||||
}
|
||||
|
||||
updateOptions() {
|
||||
|
||||
@@ -40,6 +40,7 @@ export class QueryVariable implements Variable {
|
||||
tagValuesQuery: null,
|
||||
};
|
||||
|
||||
/** @ngInject **/
|
||||
constructor(private model, private datasourceSrv, private templateSrv, private variableSrv, private $q) {
|
||||
// copy model properties to this instance
|
||||
assignModelProperties(this, model, this.defaults);
|
||||
|
||||
@@ -62,6 +62,7 @@ describe('VariableSrv init', function() {
|
||||
options: [{text: "test", value: "test"}]
|
||||
}];
|
||||
scenario.urlParams["var-apps"] = "new";
|
||||
scenario.metricSources = [];
|
||||
});
|
||||
|
||||
it('should update current value', () => {
|
||||
@@ -110,6 +111,30 @@ describe('VariableSrv init', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describeInitScenario('when datasource variable is initialized', scenario => {
|
||||
scenario.setup(() => {
|
||||
scenario.variables = [{
|
||||
type: 'datasource',
|
||||
query: 'graphite',
|
||||
name: 'test',
|
||||
current: {value: 'backend4_pee', text: 'backend4_pee'},
|
||||
regex: '/pee$/'
|
||||
}
|
||||
];
|
||||
scenario.metricSources = [
|
||||
{name: 'backend1', meta: {id: 'influx'}},
|
||||
{name: 'backend2_pee', meta: {id: 'graphite'}},
|
||||
{name: 'backend3', meta: {id: 'graphite'}},
|
||||
{name: 'backend4_pee', meta: {id: 'graphite'}},
|
||||
];
|
||||
});
|
||||
|
||||
it('should update current value', function() {
|
||||
var variable = ctx.variableSrv.variables[0];
|
||||
expect(variable.options.length).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
describeInitScenario('when template variable is present in url multiple times', scenario => {
|
||||
scenario.setup(() => {
|
||||
scenario.variables = [{
|
||||
|
||||
@@ -43,6 +43,10 @@ function (angular, _, kbn) {
|
||||
}
|
||||
};
|
||||
|
||||
this.variableInitialized = function(variable) {
|
||||
this._index[variable.name] = variable;
|
||||
};
|
||||
|
||||
this.getAdhocFilters = function(datasourceName) {
|
||||
var variable = this._adhocVariables[datasourceName];
|
||||
if (variable) {
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'app/core/utils/kbn',
|
||||
],
|
||||
function (angular, _, $, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('templateValuesSrv', function($q, $rootScope, datasourceSrv, $location, templateSrv, timeSrv) {
|
||||
var self = this;
|
||||
this.variableLock = {};
|
||||
|
||||
function getNoneOption() { return { text: 'None', value: '', isNone: true }; }
|
||||
|
||||
// update time variant variables
|
||||
$rootScope.onAppEvent('refresh', function() {
|
||||
|
||||
// look for interval variables
|
||||
var intervalVariable = _.find(self.variables, { type: 'interval' });
|
||||
if (intervalVariable) {
|
||||
self.updateAutoInterval(intervalVariable);
|
||||
}
|
||||
|
||||
// update variables with refresh === 2
|
||||
var promises = self.variables
|
||||
.filter(function(variable) {
|
||||
return variable.refresh === 2;
|
||||
}).map(function(variable) {
|
||||
var previousOptions = variable.options.slice();
|
||||
|
||||
return self.updateOptions(variable).then(function () {
|
||||
return self.variableUpdated(variable).then(function () {
|
||||
// check if current options changed due to refresh
|
||||
if (angular.toJson(previousOptions) !== angular.toJson(variable.options)) {
|
||||
$rootScope.appEvent('template-variable-value-updated');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return $q.all(promises);
|
||||
|
||||
}, $rootScope);
|
||||
|
||||
this.init = function(dashboard) {
|
||||
this.dashboard = dashboard;
|
||||
this.variables = dashboard.templating.list;
|
||||
templateSrv.init(this.variables);
|
||||
|
||||
var queryParams = $location.search();
|
||||
var promises = [];
|
||||
|
||||
// use promises to delay processing variables that
|
||||
// depend on other variables.
|
||||
this.variableLock = {};
|
||||
_.forEach(this.variables, function(variable) {
|
||||
self.variableLock[variable.name] = $q.defer();
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.variables.length; i++) {
|
||||
var variable = this.variables[i];
|
||||
promises.push(this.processVariable(variable, queryParams));
|
||||
}
|
||||
|
||||
return $q.all(promises);
|
||||
};
|
||||
|
||||
this.processVariable = function(variable, queryParams) {
|
||||
var dependencies = [];
|
||||
var lock = self.variableLock[variable.name];
|
||||
|
||||
// determine our dependencies.
|
||||
if (variable.type === "query") {
|
||||
_.forEach(this.variables, function(v) {
|
||||
// both query and datasource can contain variable
|
||||
if (templateSrv.containsVariable(variable.query, v.name) ||
|
||||
templateSrv.containsVariable(variable.datasource, v.name)) {
|
||||
dependencies.push(self.variableLock[v.name].promise);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $q.all(dependencies).then(function() {
|
||||
var urlValue = queryParams['var-' + variable.name];
|
||||
if (urlValue !== void 0) {
|
||||
return self.setVariableFromUrl(variable, urlValue).then(lock.resolve);
|
||||
}
|
||||
else if (variable.refresh === 1 || variable.refresh === 2) {
|
||||
return self.updateOptions(variable).then(function() {
|
||||
if (_.isEmpty(variable.current) && variable.options.length) {
|
||||
self.setVariableValue(variable, variable.options[0]);
|
||||
}
|
||||
lock.resolve();
|
||||
});
|
||||
}
|
||||
else if (variable.type === 'interval') {
|
||||
self.updateAutoInterval(variable);
|
||||
lock.resolve();
|
||||
} else {
|
||||
lock.resolve();
|
||||
}
|
||||
}).finally(function() {
|
||||
delete self.variableLock[variable.name];
|
||||
});
|
||||
};
|
||||
|
||||
this.setVariableFromUrl = function(variable, urlValue) {
|
||||
var promise = $q.when(true);
|
||||
|
||||
if (variable.refresh) {
|
||||
promise = this.updateOptions(variable);
|
||||
}
|
||||
|
||||
return promise.then(function() {
|
||||
var option = _.find(variable.options, function(op) {
|
||||
return op.text === urlValue || op.value === urlValue;
|
||||
});
|
||||
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
|
||||
self.updateAutoInterval(variable);
|
||||
return self.setVariableValue(variable, option, true);
|
||||
});
|
||||
};
|
||||
|
||||
this.updateAutoInterval = function(variable) {
|
||||
if (!variable.auto) { return; }
|
||||
|
||||
// add auto option if missing
|
||||
if (variable.options.length && variable.options[0].text !== 'auto') {
|
||||
variable.options.unshift({ text: 'auto', value: '$__auto_interval' });
|
||||
}
|
||||
|
||||
var interval = kbn.calculateInterval(timeSrv.timeRange(), variable.auto_count, (variable.auto_min ? ">"+variable.auto_min : null));
|
||||
templateSrv.setGrafanaVariable('$__auto_interval', interval);
|
||||
};
|
||||
|
||||
this.setVariableValue = function(variable, option) {
|
||||
variable.current = angular.copy(option);
|
||||
|
||||
if (_.isArray(variable.current.text)) {
|
||||
variable.current.text = variable.current.text.join(' + ');
|
||||
}
|
||||
|
||||
self.selectOptionsForCurrentValue(variable);
|
||||
templateSrv.updateTemplateData();
|
||||
|
||||
return this.updateOptionsInChildVariables(variable);
|
||||
};
|
||||
|
||||
this.variableUpdated = function(variable) {
|
||||
templateSrv.updateTemplateData();
|
||||
return self.updateOptionsInChildVariables(variable);
|
||||
};
|
||||
|
||||
this.updateOptionsInChildVariables = function(updatedVariable) {
|
||||
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
||||
if (self.variableLock[updatedVariable.name]) {
|
||||
return $q.when();
|
||||
}
|
||||
|
||||
var promises = _.map(self.variables, function(otherVariable) {
|
||||
if (otherVariable === updatedVariable) {
|
||||
return;
|
||||
}
|
||||
if (templateSrv.containsVariable(otherVariable.regex, updatedVariable.name) ||
|
||||
templateSrv.containsVariable(otherVariable.query, updatedVariable.name) ||
|
||||
templateSrv.containsVariable(otherVariable.datasource, updatedVariable.name)) {
|
||||
return self.updateOptions(otherVariable);
|
||||
}
|
||||
});
|
||||
|
||||
return $q.all(promises);
|
||||
};
|
||||
|
||||
this._updateNonQueryVariable = function(variable) {
|
||||
if (variable.type === 'datasource') {
|
||||
self.updateDataSourceVariable(variable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (variable.type === 'constant') {
|
||||
variable.options = [{text: variable.query, value: variable.query}];
|
||||
return;
|
||||
}
|
||||
|
||||
if (variable.type === 'adhoc') {
|
||||
variable.current = {};
|
||||
variable.options = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// extract options in comma separated string
|
||||
variable.options = _.map(variable.query.split(/[,]+/), function(text) {
|
||||
return { text: text.trim(), value: text.trim() };
|
||||
});
|
||||
|
||||
if (variable.type === 'interval') {
|
||||
self.updateAutoInterval(variable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (variable.type === 'custom' && variable.includeAll) {
|
||||
self.addAllOption(variable);
|
||||
}
|
||||
};
|
||||
|
||||
this.updateDataSourceVariable = function(variable) {
|
||||
var options = [];
|
||||
var sources = datasourceSrv.getMetricSources({skipVariables: true});
|
||||
var regex;
|
||||
|
||||
if (variable.regex) {
|
||||
regex = kbn.stringToJsRegex(templateSrv.replace(variable.regex));
|
||||
}
|
||||
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var source = sources[i];
|
||||
// must match on type
|
||||
if (source.meta.id !== variable.query) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (regex && !regex.exec(source.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
options.push({text: source.name, value: source.name});
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
options.push({text: 'No data sources found', value: ''});
|
||||
}
|
||||
|
||||
variable.options = options;
|
||||
};
|
||||
|
||||
this.updateOptions = function(variable) {
|
||||
if (variable.type !== 'query') {
|
||||
self._updateNonQueryVariable(variable);
|
||||
return self.validateVariableSelectionState(variable);
|
||||
}
|
||||
|
||||
return datasourceSrv.get(variable.datasource)
|
||||
.then(_.partial(this.updateOptionsFromMetricFindQuery, variable))
|
||||
.then(_.partial(this.updateTags, variable))
|
||||
.then(_.partial(this.validateVariableSelectionState, variable));
|
||||
};
|
||||
|
||||
this.selectOptionsForCurrentValue = function(variable) {
|
||||
var i, y, value, option;
|
||||
var selected = [];
|
||||
|
||||
for (i = 0; i < variable.options.length; i++) {
|
||||
option = variable.options[i];
|
||||
option.selected = false;
|
||||
if (_.isArray(variable.current.value)) {
|
||||
for (y = 0; y < variable.current.value.length; y++) {
|
||||
value = variable.current.value[y];
|
||||
if (option.value === value) {
|
||||
option.selected = true;
|
||||
selected.push(option);
|
||||
}
|
||||
}
|
||||
} else if (option.value === variable.current.value) {
|
||||
option.selected = true;
|
||||
selected.push(option);
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
};
|
||||
|
||||
this.validateVariableSelectionState = function(variable) {
|
||||
if (!variable.current) {
|
||||
if (!variable.options.length) { return $q.when(); }
|
||||
return self.setVariableValue(variable, variable.options[0], false);
|
||||
}
|
||||
|
||||
if (_.isArray(variable.current.value)) {
|
||||
var selected = self.selectOptionsForCurrentValue(variable);
|
||||
|
||||
// if none pick first
|
||||
if (selected.length === 0) {
|
||||
selected = variable.options[0];
|
||||
} else {
|
||||
selected = {
|
||||
value: _.map(selected, function(val) {return val.value;}),
|
||||
text: _.map(selected, function(val) {return val.text;}).join(' + '),
|
||||
};
|
||||
}
|
||||
|
||||
return self.setVariableValue(variable, selected, false);
|
||||
} else {
|
||||
var currentOption = _.find(variable.options, {text: variable.current.text});
|
||||
if (currentOption) {
|
||||
return self.setVariableValue(variable, currentOption, false);
|
||||
} else {
|
||||
if (!variable.options.length) { return $q.when(null); }
|
||||
return self.setVariableValue(variable, variable.options[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.updateTags = function(variable, datasource) {
|
||||
if (variable.useTags) {
|
||||
return datasource.metricFindQuery(variable.tagsQuery).then(function (results) {
|
||||
variable.tags = [];
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
variable.tags.push(results[i].text);
|
||||
}
|
||||
return datasource;
|
||||
});
|
||||
} else {
|
||||
delete variable.tags;
|
||||
}
|
||||
|
||||
return datasource;
|
||||
};
|
||||
|
||||
this.updateOptionsFromMetricFindQuery = function(variable, datasource) {
|
||||
return datasource.metricFindQuery(variable.query).then(function (results) {
|
||||
variable.options = self.metricNamesToVariableValues(variable, results);
|
||||
if (variable.includeAll) {
|
||||
self.addAllOption(variable);
|
||||
}
|
||||
if (!variable.options.length) {
|
||||
variable.options.push(getNoneOption());
|
||||
}
|
||||
return datasource;
|
||||
});
|
||||
};
|
||||
|
||||
this.getValuesForTag = function(variable, tagKey) {
|
||||
return datasourceSrv.get(variable.datasource).then(function(datasource) {
|
||||
var query = variable.tagValuesQuery.replace('$tag', tagKey);
|
||||
return datasource.metricFindQuery(query).then(function (results) {
|
||||
return _.map(results, function(value) {
|
||||
return value.text;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.metricNamesToVariableValues = function(variable, metricNames) {
|
||||
var regex, options, i, matches;
|
||||
options = [];
|
||||
|
||||
if (variable.regex) {
|
||||
regex = kbn.stringToJsRegex(templateSrv.replace(variable.regex));
|
||||
}
|
||||
|
||||
for (i = 0; i < metricNames.length; i++) {
|
||||
var item = metricNames[i];
|
||||
var value = item.value || item.text;
|
||||
var text = item.text || item.value;
|
||||
|
||||
if (_.isNumber(value)) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (_.isNumber(text)) {
|
||||
text = text.toString();
|
||||
}
|
||||
|
||||
if (regex) {
|
||||
matches = regex.exec(value);
|
||||
if (!matches) { continue; }
|
||||
if (matches.length > 1) {
|
||||
value = matches[1];
|
||||
text = value;
|
||||
}
|
||||
}
|
||||
|
||||
options.push({text: text, value: value});
|
||||
}
|
||||
|
||||
options = _.uniq(options, 'value');
|
||||
return this.sortVariableValues(options, variable.sort);
|
||||
};
|
||||
|
||||
this.addAllOption = function(variable) {
|
||||
variable.options.unshift({text: 'All', value: "$__all"});
|
||||
};
|
||||
|
||||
this.sortVariableValues = function(options, sortOrder) {
|
||||
if (sortOrder === 0) {
|
||||
return options;
|
||||
}
|
||||
|
||||
var sortType = Math.ceil(sortOrder / 2);
|
||||
var reverseSort = (sortOrder % 2 === 0);
|
||||
if (sortType === 1) {
|
||||
options = _.sortBy(options, 'text');
|
||||
} else if (sortType === 2) {
|
||||
options = _.sortBy(options, function(opt) {
|
||||
var matches = opt.text.match(/.*?(\d+).*/);
|
||||
if (!matches) {
|
||||
return 0;
|
||||
} else {
|
||||
return parseInt(matches[1], 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (reverseSort) {
|
||||
options = options.reverse();
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import {Variable, variableTypes} from './variable';
|
||||
export class VariableSrv {
|
||||
dashboard: any;
|
||||
variables: any;
|
||||
variableLock: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope, private $q, private $location, private $injector, private templateSrv) {
|
||||
@@ -18,7 +17,6 @@ export class VariableSrv {
|
||||
}
|
||||
|
||||
init(dashboard) {
|
||||
this.variableLock = {};
|
||||
this.dashboard = dashboard;
|
||||
|
||||
// create working class models representing variables
|
||||
@@ -30,13 +28,15 @@ export class VariableSrv {
|
||||
|
||||
// init variables
|
||||
for (let variable of this.variables) {
|
||||
this.variableLock[variable.name] = this.$q.defer();
|
||||
variable.initLock = this.$q.defer();
|
||||
}
|
||||
|
||||
var queryParams = this.$location.search();
|
||||
return this.$q.all(this.variables.map(variable => {
|
||||
return this.processVariable(variable, queryParams);
|
||||
}));
|
||||
})).then(() => {
|
||||
this.templateSrv.updateTemplateData();
|
||||
});
|
||||
}
|
||||
|
||||
onDashboardRefresh() {
|
||||
@@ -59,27 +59,27 @@ export class VariableSrv {
|
||||
|
||||
processVariable(variable, queryParams) {
|
||||
var dependencies = [];
|
||||
var lock = this.variableLock[variable.name];
|
||||
|
||||
for (let otherVariable of this.variables) {
|
||||
if (variable.dependsOn(otherVariable)) {
|
||||
dependencies.push(this.variableLock[otherVariable.name].promise);
|
||||
dependencies.push(otherVariable.initLock.promise);
|
||||
}
|
||||
}
|
||||
|
||||
return this.$q.all(dependencies).then(() => {
|
||||
var urlValue = queryParams['var-' + variable.name];
|
||||
if (urlValue !== void 0) {
|
||||
return variable.setValueFromUrl(urlValue).then(lock.resolve);
|
||||
return variable.setValueFromUrl(urlValue).then(variable.initLock.resolve);
|
||||
}
|
||||
|
||||
if (variable.refresh === 1 || variable.refresh === 2) {
|
||||
return variable.updateOptions().then(lock.resolve);
|
||||
return variable.updateOptions().then(variable.initLock.resolve);
|
||||
}
|
||||
|
||||
lock.resolve();
|
||||
variable.initLock.resolve();
|
||||
}).finally(() => {
|
||||
delete this.variableLock[variable.name];
|
||||
this.templateSrv.variableInitialized(variable);
|
||||
delete variable.initLock;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export class VariableSrv {
|
||||
|
||||
variableUpdated(variable) {
|
||||
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
||||
if (this.variableLock[variable.name]) {
|
||||
if (variable.initLock) {
|
||||
return this.$q.when();
|
||||
}
|
||||
|
||||
@@ -155,8 +155,7 @@ export class VariableSrv {
|
||||
|
||||
validateVariableSelectionState(variable) {
|
||||
if (!variable.current) {
|
||||
if (!variable.options.length) { return this.$q.when(); }
|
||||
return variable.setValue(variable.options[0]);
|
||||
variable.current = {};
|
||||
}
|
||||
|
||||
if (_.isArray(variable.current.value)) {
|
||||
|
||||
287
public/app/plugins/app/testdata/dashboards/alerts.json
vendored
Normal file
287
public/app/plugins/app/testdata/dashboards/alerts.json
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"revision": 2,
|
||||
"title": "TestData - Alerts",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
],
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"hideControls": false,
|
||||
"sharedCrosshair": false,
|
||||
"rows": [
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": 255.625,
|
||||
"panels": [
|
||||
{
|
||||
"alert": {
|
||||
"conditions": [
|
||||
{
|
||||
"evaluator": {
|
||||
"params": [
|
||||
60
|
||||
],
|
||||
"type": "gt"
|
||||
},
|
||||
"query": {
|
||||
"params": [
|
||||
"A",
|
||||
"5m",
|
||||
"now"
|
||||
]
|
||||
},
|
||||
"reducer": {
|
||||
"params": [],
|
||||
"type": "avg"
|
||||
},
|
||||
"type": "query"
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"frequency": "60s",
|
||||
"handler": 1,
|
||||
"name": "TestData - Always OK",
|
||||
"noDataState": "no_data",
|
||||
"notifications": []
|
||||
},
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 3,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [
|
||||
{
|
||||
"value": 60,
|
||||
"op": "gt",
|
||||
"fill": true,
|
||||
"line": true,
|
||||
"colorMode": "critical"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Always OK",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": "",
|
||||
"logBase": 1,
|
||||
"max": "125",
|
||||
"min": "0",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"alert": {
|
||||
"conditions": [
|
||||
{
|
||||
"evaluator": {
|
||||
"params": [
|
||||
177
|
||||
],
|
||||
"type": "gt"
|
||||
},
|
||||
"query": {
|
||||
"params": [
|
||||
"A",
|
||||
"5m",
|
||||
"now"
|
||||
]
|
||||
},
|
||||
"reducer": {
|
||||
"params": [],
|
||||
"type": "avg"
|
||||
},
|
||||
"type": "query"
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"frequency": "60s",
|
||||
"handler": 1,
|
||||
"name": "TestData - Always Alerting",
|
||||
"noDataState": "no_data",
|
||||
"notifications": []
|
||||
},
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 4,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "200,445,100,150,200,220,190",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [
|
||||
{
|
||||
"colorMode": "critical",
|
||||
"fill": true,
|
||||
"line": true,
|
||||
"op": "gt",
|
||||
"value": 177
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Always Alerting",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": "",
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": "0",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": "",
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"schemaVersion": 13,
|
||||
"version": 4,
|
||||
"links": [],
|
||||
"gnetId": null
|
||||
}
|
||||
483
public/app/plugins/app/testdata/dashboards/graph_last_1h.json
vendored
Normal file
483
public/app/plugins/app/testdata/dashboards/graph_last_1h.json
vendored
Normal file
@@ -0,0 +1,483 @@
|
||||
{
|
||||
"revision": 4,
|
||||
"title": "TestData - Graph Panel Last 1h",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
],
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"editable": true,
|
||||
"hideControls": false,
|
||||
"sharedCrosshair": false,
|
||||
"rows": [
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": "250px",
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 1,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 4,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "no_data_points",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "No Data Points Warning",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 2,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 4,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "datapoints_outside_range",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Datapoints Outside Range Warning",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 3,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 4,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "random_walk",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Random walk series",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": "250px",
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 4,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "connected",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 8,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenario": "random_walk",
|
||||
"scenarioId": "random_walk",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": "2s",
|
||||
"timeShift": null,
|
||||
"title": "Millisecond res x-axis and tooltip",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"error": false,
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"isNew": true,
|
||||
"id": 6,
|
||||
"mode": "markdown",
|
||||
"content": "Just verify that the tooltip time has millisecond resolution ",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
},
|
||||
{
|
||||
"title": "New row",
|
||||
"height": 336,
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"panels": [
|
||||
{
|
||||
"title": "2 yaxis and axis lables",
|
||||
"error": false,
|
||||
"span": 7.99561403508772,
|
||||
"editable": true,
|
||||
"type": "graph",
|
||||
"isNew": true,
|
||||
"id": 5,
|
||||
"targets": [
|
||||
{
|
||||
"target": "",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
},
|
||||
{
|
||||
"target": "",
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "2000,3000,4000,1000,3000,10000"
|
||||
}
|
||||
],
|
||||
"datasource": "Grafana TestData",
|
||||
"renderer": "flot",
|
||||
"yaxes": [
|
||||
{
|
||||
"label": "Perecent",
|
||||
"show": true,
|
||||
"logBase": 1,
|
||||
"min": null,
|
||||
"max": null,
|
||||
"format": "percent"
|
||||
},
|
||||
{
|
||||
"label": "Pressure",
|
||||
"show": true,
|
||||
"logBase": 1,
|
||||
"min": null,
|
||||
"max": null,
|
||||
"format": "short"
|
||||
}
|
||||
],
|
||||
"xaxis": {
|
||||
"show": true,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"values": []
|
||||
},
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"percentage": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"msResolution": false
|
||||
},
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"aliasColors": {},
|
||||
"seriesOverrides": [
|
||||
{
|
||||
"alias": "B-series",
|
||||
"yaxis": 2
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"error": false,
|
||||
"span": 4.00438596491228,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"isNew": true,
|
||||
"id": 7,
|
||||
"mode": "markdown",
|
||||
"content": "Verify that axis labels look ok",
|
||||
"links": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"refresh": false,
|
||||
"schemaVersion": 13,
|
||||
"version": 3,
|
||||
"links": [],
|
||||
"gnetId": null
|
||||
}
|
||||
62
public/app/plugins/app/testdata/datasource/datasource.ts
vendored
Normal file
62
public/app/plugins/app/testdata/datasource/datasource.ts
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
class TestDataDatasource {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $q) {}
|
||||
|
||||
query(options) {
|
||||
var queries = _.filter(options.targets, item => {
|
||||
return item.hide !== true;
|
||||
}).map(item => {
|
||||
return {
|
||||
refId: item.refId,
|
||||
scenarioId: item.scenarioId,
|
||||
intervalMs: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
stringInput: item.stringInput,
|
||||
jsonInput: angular.fromJson(item.jsonInput),
|
||||
};
|
||||
});
|
||||
|
||||
if (queries.length === 0) {
|
||||
return this.$q.when({data: []});
|
||||
}
|
||||
|
||||
return this.backendSrv.post('/api/tsdb/query', {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: queries,
|
||||
}).then(res => {
|
||||
var data = [];
|
||||
|
||||
if (res.results) {
|
||||
_.forEach(res.results, queryRes => {
|
||||
for (let series of queryRes.series) {
|
||||
data.push({
|
||||
target: series.name,
|
||||
datapoints: series.points
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {data: data};
|
||||
});
|
||||
}
|
||||
|
||||
annotationQuery(options) {
|
||||
return this.backendSrv.get('/api/annotations', {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
limit: options.limit,
|
||||
type: options.type,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {TestDataDatasource};
|
||||
22
public/app/plugins/app/testdata/datasource/module.ts
vendored
Normal file
22
public/app/plugins/app/testdata/datasource/module.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import {TestDataDatasource} from './datasource';
|
||||
import {TestDataQueryCtrl} from './query_ctrl';
|
||||
|
||||
class TestDataAnnotationsQueryCtrl {
|
||||
annotation: any;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
static template = '<h2>test data</h2>';
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
TestDataDatasource,
|
||||
TestDataDatasource as Datasource,
|
||||
TestDataQueryCtrl as QueryCtrl,
|
||||
TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl,
|
||||
};
|
||||
|
||||
20
public/app/plugins/app/testdata/datasource/plugin.json
vendored
Normal file
20
public/app/plugins/app/testdata/datasource/plugin.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Grafana TestDataDB",
|
||||
"id": "grafana-testdata-datasource",
|
||||
|
||||
"metrics": true,
|
||||
"alerting": true,
|
||||
"annotations": true,
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
},
|
||||
"logos": {
|
||||
"small": "",
|
||||
"large": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
35
public/app/plugins/app/testdata/datasource/query_ctrl.ts
vendored
Normal file
35
public/app/plugins/app/testdata/datasource/query_ctrl.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import {TestDataDatasource} from './datasource';
|
||||
import {QueryCtrl} from 'app/plugins/sdk';
|
||||
|
||||
export class TestDataQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
scenarioList: any;
|
||||
scenario: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor($scope, $injector, private backendSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
this.target.scenarioId = this.target.scenarioId || 'random_walk';
|
||||
this.scenarioList = [];
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.backendSrv.get('/api/tsdb/testdata/scenarios').then(res => {
|
||||
this.scenarioList = res;
|
||||
this.scenario = _.find(this.scenarioList, {id: this.target.scenarioId});
|
||||
});
|
||||
}
|
||||
|
||||
scenarioChanged() {
|
||||
this.scenario = _.find(this.scenarioList, {id: this.target.scenarioId});
|
||||
this.target.stringInput = this.scenario.stringInput;
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
36
public/app/plugins/app/testdata/module.ts
vendored
Normal file
36
public/app/plugins/app/testdata/module.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
export class ConfigCtrl {
|
||||
static template = '';
|
||||
|
||||
appEditCtrl: any;
|
||||
|
||||
constructor(private backendSrv) {
|
||||
this.appEditCtrl.setPreUpdateHook(this.initDatasource.bind(this));
|
||||
}
|
||||
|
||||
initDatasource() {
|
||||
return this.backendSrv.get('/api/datasources').then(res => {
|
||||
var found = false;
|
||||
for (let ds of res) {
|
||||
if (ds.type === "grafana-testdata-datasource") {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
var dsInstance = {
|
||||
name: 'Grafana TestData',
|
||||
type: 'grafana-testdata-datasource',
|
||||
access: 'direct',
|
||||
jsonData: {}
|
||||
};
|
||||
|
||||
return this.backendSrv.post('/api/datasources', dsInstance);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
22
public/app/plugins/app/testdata/partials/query.editor.html
vendored
Normal file
22
public/app/plugins/app/testdata/partials/query.editor.html
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="false">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword">Scenario</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.target.scenarioId" ng-options="v.id as v.name for v in ctrl.scenarioList" ng-change="ctrl.scenarioChanged()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form gf-form--grow" ng-if="ctrl.scenario.stringInput">
|
||||
<label class="gf-form-label query-keyword">String Input</label>
|
||||
<input type="text" class="gf-form-input" placeholder="{{ctrl.scenario.stringInput}}" ng-model="ctrl.target.stringInput" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword">Alias</label>
|
||||
<input type="text" class="gf-form-input max-width-7" placeholder="optional" ng-model="ctrl.target.alias" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</query-editor-row>
|
||||
|
||||
32
public/app/plugins/app/testdata/plugin.json
vendored
Normal file
32
public/app/plugins/app/testdata/plugin.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"type": "app",
|
||||
"name": "Grafana TestData",
|
||||
"id": "testdata",
|
||||
|
||||
"info": {
|
||||
"description": "Grafana test data app",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
},
|
||||
"version": "1.0.13",
|
||||
"updated": "2016-09-26"
|
||||
},
|
||||
|
||||
"includes": [
|
||||
{
|
||||
"type": "dashboard",
|
||||
"name": "TestData - Graph Last 1h",
|
||||
"path": "dashboards/graph_last_1h.json"
|
||||
},
|
||||
{
|
||||
"type": "dashboard",
|
||||
"name": "TestData - Alerts",
|
||||
"path": "dashboards/alerts.json"
|
||||
}
|
||||
],
|
||||
|
||||
"dependencies": {
|
||||
"grafanaVersion": "4.x.x"
|
||||
}
|
||||
}
|
||||
@@ -216,11 +216,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
});
|
||||
};
|
||||
|
||||
function escapeForJson(value) {
|
||||
var luceneQuery = JSON.stringify(value);
|
||||
return luceneQuery.substr(1, luceneQuery.length - 2);
|
||||
}
|
||||
|
||||
this.getFields = function(query) {
|
||||
return this._get('/_mapping').then(function(result) {
|
||||
var typeMap = {
|
||||
@@ -285,7 +280,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
var header = this.getQueryHeader('count', range.from, range.to);
|
||||
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
|
||||
|
||||
esQuery = esQuery.replace("$lucene_query", escapeForJson(queryDef.query));
|
||||
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
|
||||
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
|
||||
esQuery = header + '\n' + esQuery + '\n';
|
||||
|
||||
@@ -221,12 +221,6 @@ function (queryDef) {
|
||||
"size": 0,
|
||||
"query": {
|
||||
"filtered": {
|
||||
"query": {
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": '$lucene_query',
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"bool": {
|
||||
"must": [{"range": this.getRangeFilter()}]
|
||||
@@ -235,6 +229,16 @@ function (queryDef) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (queryDef.query) {
|
||||
query.query.filtered.query = {
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": queryDef.query,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
query.aggs = {
|
||||
"1": {
|
||||
"terms": {
|
||||
|
||||
@@ -9,6 +9,8 @@ class GrafanaDatasource {
|
||||
return this.backendSrv.get('/api/metrics/test', {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
scenario: 'random_walk',
|
||||
interval: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,13 +134,7 @@ for (var i = 0; i < 128; i++) {
|
||||
i >= 97 && i <= 122; // a-z
|
||||
}
|
||||
|
||||
var identifierPartTable = [];
|
||||
|
||||
for (var i2 = 0; i2 < 128; i2++) {
|
||||
identifierPartTable[i2] =
|
||||
identifierStartTable[i2] || // $, _, A-Z, a-z
|
||||
i2 >= 48 && i2 <= 57; // 0-9
|
||||
}
|
||||
var identifierPartTable = identifierStartTable;
|
||||
|
||||
export function Lexer(expression) {
|
||||
this.input = expression;
|
||||
@@ -423,256 +417,260 @@ Lexer.prototype = {
|
||||
if (char === '-') {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
}
|
||||
char = this.peek(index);
|
||||
}
|
||||
|
||||
// Numbers must start either with a decimal digit or a point.
|
||||
if (char !== "." && !isDecimalDigit(char)) {
|
||||
return null;
|
||||
}
|
||||
// Numbers must start either with a decimal digit or a point.
|
||||
if (char !== "." && !isDecimalDigit(char)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (char !== ".") {
|
||||
value += this.peek(index);
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
if (char !== ".") {
|
||||
value += this.peek(index);
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
|
||||
if (value === "0") {
|
||||
// Base-16 numbers.
|
||||
if (char === "x" || char === "X") {
|
||||
index += 1;
|
||||
value += char;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isHexDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if (value.length <= 2) { // 0x
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
isMalformed: true,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (isIdentifierStart(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
base: 16,
|
||||
isMalformed: false,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
// Base-8 numbers.
|
||||
if (isOctalDigit(char)) {
|
||||
index += 1;
|
||||
value += char;
|
||||
bad = false;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
|
||||
// Numbers like '019' (note the 9) are not valid octals
|
||||
// but we still parse them and mark as malformed.
|
||||
|
||||
if (isDecimalDigit(char)) {
|
||||
bad = true;
|
||||
} else if (!isOctalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (isIdentifierStart(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
base: 8,
|
||||
isMalformed: false
|
||||
};
|
||||
}
|
||||
|
||||
// Decimal numbers that start with '0' such as '09' are illegal
|
||||
// but we still parse them and return as malformed.
|
||||
|
||||
if (isDecimalDigit(char)) {
|
||||
index += 1;
|
||||
value += char;
|
||||
}
|
||||
}
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
if (value === "0") {
|
||||
// Base-16 numbers.
|
||||
if (char === "x" || char === "X") {
|
||||
index += 1;
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Decimal digits.
|
||||
|
||||
if (char === ".") {
|
||||
value += char;
|
||||
index += 1;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Exponent part.
|
||||
|
||||
if (char === "e" || char === "E") {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
|
||||
if (char === "+" || char === "-") {
|
||||
value += this.peek(index);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
char = this.peek(index);
|
||||
if (isDecimalDigit(char)) {
|
||||
value += char;
|
||||
index += 1;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
if (!isHexDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!this.isPunctuator(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (value.length <= 2) { // 0x
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
isMalformed: true,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
base: 10,
|
||||
pos: this.char,
|
||||
isMalformed: !isFinite(+value)
|
||||
};
|
||||
},
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (isIdentifierStart(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
isPunctuator: function (ch1) {
|
||||
switch (ch1) {
|
||||
case ".":
|
||||
case "(":
|
||||
case ")":
|
||||
case ",":
|
||||
case "{":
|
||||
case "}":
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
scanPunctuator: function () {
|
||||
var ch1 = this.peek();
|
||||
|
||||
if (this.isPunctuator(ch1)) {
|
||||
return {
|
||||
type: ch1,
|
||||
value: ch1,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a string out of the next sequence of characters and/or
|
||||
* lines or return 'null' if its not possible. Since strings can
|
||||
* span across multiple lines this method has to move the char
|
||||
* pointer.
|
||||
*
|
||||
* This method recognizes pseudo-multiline JavaScript strings:
|
||||
*
|
||||
* var str = "hello\
|
||||
* world";
|
||||
*/
|
||||
scanStringLiteral: function () {
|
||||
/*jshint loopfunc:true */
|
||||
var quote = this.peek();
|
||||
|
||||
// String must start with a quote.
|
||||
if (quote !== "\"" && quote !== "'") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = "";
|
||||
|
||||
this.skip();
|
||||
|
||||
while (this.peek() !== quote) {
|
||||
if (this.peek() === "") { // End Of Line
|
||||
return {
|
||||
type: 'string',
|
||||
type: 'number',
|
||||
value: value,
|
||||
isUnclosed: true,
|
||||
quote: quote,
|
||||
base: 16,
|
||||
isMalformed: false,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
var char = this.peek();
|
||||
var jump = 1; // A length of a jump, after we're done
|
||||
// parsing this character.
|
||||
// Base-8 numbers.
|
||||
if (isOctalDigit(char)) {
|
||||
index += 1;
|
||||
value += char;
|
||||
bad = false;
|
||||
|
||||
value += char;
|
||||
this.skip(jump);
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
|
||||
// Numbers like '019' (note the 9) are not valid octals
|
||||
// but we still parse them and mark as malformed.
|
||||
|
||||
if (isDecimalDigit(char)) {
|
||||
bad = true;
|
||||
} if (!isOctalDigit(char)) {
|
||||
// if the char is a non punctuator then its not a valid number
|
||||
if (!this.isPunctuator(char)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (isIdentifierStart(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
base: 8,
|
||||
isMalformed: bad
|
||||
};
|
||||
}
|
||||
|
||||
// Decimal numbers that start with '0' such as '09' are illegal
|
||||
// but we still parse them and return as malformed.
|
||||
|
||||
if (isDecimalDigit(char)) {
|
||||
index += 1;
|
||||
value += char;
|
||||
}
|
||||
}
|
||||
|
||||
this.skip();
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Decimal digits.
|
||||
|
||||
if (char === ".") {
|
||||
value += char;
|
||||
index += 1;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Exponent part.
|
||||
|
||||
if (char === "e" || char === "E") {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
|
||||
if (char === "+" || char === "-") {
|
||||
value += this.peek(index);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
char = this.peek(index);
|
||||
if (isDecimalDigit(char)) {
|
||||
value += char;
|
||||
index += 1;
|
||||
|
||||
while (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!isDecimalDigit(char)) {
|
||||
break;
|
||||
}
|
||||
value += char;
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (!this.isPunctuator(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: value,
|
||||
base: 10,
|
||||
pos: this.char,
|
||||
isMalformed: !isFinite(+value)
|
||||
};
|
||||
},
|
||||
|
||||
isPunctuator: function (ch1) {
|
||||
switch (ch1) {
|
||||
case ".":
|
||||
case "(":
|
||||
case ")":
|
||||
case ",":
|
||||
case "{":
|
||||
case "}":
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
scanPunctuator: function () {
|
||||
var ch1 = this.peek();
|
||||
|
||||
if (this.isPunctuator(ch1)) {
|
||||
return {
|
||||
type: 'string',
|
||||
value: value,
|
||||
isUnclosed: false,
|
||||
quote: quote,
|
||||
type: ch1,
|
||||
value: ch1,
|
||||
pos: this.char
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
};
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a string out of the next sequence of characters and/or
|
||||
* lines or return 'null' if its not possible. Since strings can
|
||||
* span across multiple lines this method has to move the char
|
||||
* pointer.
|
||||
*
|
||||
* This method recognizes pseudo-multiline JavaScript strings:
|
||||
*
|
||||
* var str = "hello\
|
||||
* world";
|
||||
*/
|
||||
scanStringLiteral: function () {
|
||||
/*jshint loopfunc:true */
|
||||
var quote = this.peek();
|
||||
|
||||
// String must start with a quote.
|
||||
if (quote !== "\"" && quote !== "'") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = "";
|
||||
|
||||
this.skip();
|
||||
|
||||
while (this.peek() !== quote) {
|
||||
if (this.peek() === "") { // End Of Line
|
||||
return {
|
||||
type: 'string',
|
||||
value: value,
|
||||
isUnclosed: true,
|
||||
quote: quote,
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
var char = this.peek();
|
||||
var jump = 1; // A length of a jump, after we're done
|
||||
// parsing this character.
|
||||
|
||||
value += char;
|
||||
this.skip(jump);
|
||||
}
|
||||
|
||||
this.skip();
|
||||
return {
|
||||
type: 'string',
|
||||
value: value,
|
||||
isUnclosed: false,
|
||||
quote: quote,
|
||||
pos: this.char
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -100,10 +100,7 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
metricExpression: function() {
|
||||
if (!this.match('templateStart') &&
|
||||
!this.match('identifier') &&
|
||||
!this.match('number') &&
|
||||
!this.match('{')) {
|
||||
if (!this.match('templateStart') && !this.match('identifier') && !this.match('number') && !this.match('{')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
],
|
||||
|
||||
"metrics": true,
|
||||
"alerting": true,
|
||||
"annotations": true,
|
||||
|
||||
"info": {
|
||||
@@ -20,4 +21,4 @@
|
||||
"large": "img/graphite_logo.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,14 @@ describe('when lexing graphite expression', function() {
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with segment that start with number', function() {
|
||||
var lexer = new Lexer("metric.001-server");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens.length).to.be(3);
|
||||
});
|
||||
|
||||
it('should tokenize func call with numbered metric and number arg', function() {
|
||||
var lexer = new Lexer("scale(metric.10, 15)");
|
||||
var tokens = lexer.tokenize();
|
||||
|
||||
@@ -113,7 +113,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
throw response.error;
|
||||
}
|
||||
delete self.lastErrors.query;
|
||||
|
||||
_.each(response.data.data.result, function(metricData) {
|
||||
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
|
||||
});
|
||||
@@ -124,6 +123,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
};
|
||||
|
||||
this.performTimeSeriesQuery = function(query, start, end) {
|
||||
if (start > end) {
|
||||
throw { message: 'Invalid time range' };
|
||||
}
|
||||
|
||||
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
|
||||
return this._request('GET', url, query.requestId);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
],
|
||||
|
||||
"metrics": true,
|
||||
"alerting": true,
|
||||
"annotations": true,
|
||||
|
||||
"info": {
|
||||
|
||||
@@ -40,6 +40,32 @@
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">X-Axis</h5>
|
||||
<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-5">Mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisOptionChanged()"> </select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-5">Name</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.name" get-options="ctrl.getDataFieldNames(false)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-5">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" get-options="ctrl.getDataFieldNames(true)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
|
||||
<label class="gf-form-label width-5">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
85
public/app/plugins/panel/graph/axes_editor.ts
Normal file
85
public/app/plugins/panel/graph/axes_editor.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
|
||||
export class AxesEditorCtrl {
|
||||
panel: any;
|
||||
panelCtrl: any;
|
||||
unitFormats: any;
|
||||
logScales: any;
|
||||
xAxisModes: any;
|
||||
xAxisStatOptions: any;
|
||||
xNameSegment: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor(private $scope, private $q) {
|
||||
this.panelCtrl = $scope.ctrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
$scope.ctrl = this;
|
||||
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
|
||||
this.logScales = {
|
||||
'linear': 1,
|
||||
'log (base 2)': 2,
|
||||
'log (base 10)': 10,
|
||||
'log (base 32)': 32,
|
||||
'log (base 1024)': 1024
|
||||
};
|
||||
|
||||
this.xAxisModes = {
|
||||
'Time': 'time',
|
||||
'Series': 'series',
|
||||
// 'Data field': 'field',
|
||||
};
|
||||
|
||||
this.xAxisStatOptions = [
|
||||
{text: 'Avg', value: 'avg'},
|
||||
{text: 'Min', value: 'min'},
|
||||
{text: 'Max', value: 'min'},
|
||||
{text: 'Total', value: 'total'},
|
||||
{text: 'Count', value: 'count'},
|
||||
];
|
||||
|
||||
if (this.panel.xaxis.mode === 'custom') {
|
||||
if (!this.panel.xaxis.name) {
|
||||
this.panel.xaxis.name = 'specify field';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setUnitFormat(axis, subItem) {
|
||||
axis.format = subItem.value;
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
||||
xAxisOptionChanged() {
|
||||
this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode();
|
||||
this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
|
||||
}
|
||||
|
||||
getDataFieldNames(onlyNumbers) {
|
||||
var props = this.panelCtrl.processor.getDataFieldNames(this.panelCtrl.dataList, onlyNumbers);
|
||||
var items = props.map(prop => {
|
||||
return {text: prop, value: prop};
|
||||
});
|
||||
|
||||
return this.$q.when(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** @ngInject **/
|
||||
export function axesEditorComponent() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: true,
|
||||
templateUrl: 'public/app/plugins/panel/graph/axes_editor.html',
|
||||
controller: AxesEditorCtrl,
|
||||
};
|
||||
}
|
||||
192
public/app/plugins/panel/graph/data_processor.ts
Normal file
192
public/app/plugins/panel/graph/data_processor.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import {colors} from 'app/core/core';
|
||||
|
||||
export class DataProcessor {
|
||||
|
||||
constructor(private panel) {
|
||||
}
|
||||
|
||||
getSeriesList(options) {
|
||||
if (!options.dataList || options.dataList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// auto detect xaxis mode
|
||||
var firstItem;
|
||||
if (options.dataList && options.dataList.length > 0) {
|
||||
firstItem = options.dataList[0];
|
||||
let autoDetectMode = this.getAutoDetectXAxisMode(firstItem);
|
||||
if (this.panel.xaxis.mode !== autoDetectMode) {
|
||||
this.panel.xaxis.mode = autoDetectMode;
|
||||
this.setPanelDefaultsForNewXAxisMode();
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.panel.xaxis.mode) {
|
||||
case 'series':
|
||||
case 'time': {
|
||||
return options.dataList.map((item, index) => {
|
||||
return this.timeSeriesHandler(item, index, options);
|
||||
});
|
||||
}
|
||||
case 'field': {
|
||||
return this.customHandler(firstItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAutoDetectXAxisMode(firstItem) {
|
||||
switch (firstItem.type) {
|
||||
case 'docs': return 'field';
|
||||
case 'table': return 'field';
|
||||
default: {
|
||||
if (this.panel.xaxis.mode === 'series') {
|
||||
return 'series';
|
||||
}
|
||||
return 'time';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPanelDefaultsForNewXAxisMode() {
|
||||
switch (this.panel.xaxis.mode) {
|
||||
case 'time': {
|
||||
this.panel.bars = false;
|
||||
this.panel.lines = true;
|
||||
this.panel.points = false;
|
||||
this.panel.legend.show = true;
|
||||
this.panel.tooltip.shared = true;
|
||||
this.panel.xaxis.values = [];
|
||||
break;
|
||||
}
|
||||
case 'series': {
|
||||
this.panel.bars = true;
|
||||
this.panel.lines = false;
|
||||
this.panel.points = false;
|
||||
this.panel.stack = false;
|
||||
this.panel.legend.show = false;
|
||||
this.panel.tooltip.shared = false;
|
||||
this.panel.xaxis.values = ['total'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeSeriesHandler(seriesData, index, options) {
|
||||
var datapoints = seriesData.datapoints || [];
|
||||
var alias = seriesData.target;
|
||||
|
||||
var colorIndex = index % colors.length;
|
||||
var color = this.panel.aliasColors[alias] || colors[colorIndex];
|
||||
|
||||
var series = new TimeSeries({datapoints: datapoints, alias: alias, color: color, unit: seriesData.unit});
|
||||
|
||||
if (datapoints && datapoints.length > 0) {
|
||||
var last = datapoints[datapoints.length - 1][1];
|
||||
var from = options.range.from;
|
||||
if (last - from < -10000) {
|
||||
series.isOutsideRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
customHandler(dataItem) {
|
||||
let nameField = this.panel.xaxis.name;
|
||||
if (!nameField) {
|
||||
throw {message: 'No field name specified to use for x-axis, check your axes settings'};
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
validateXAxisSeriesValue() {
|
||||
switch (this.panel.xaxis.mode) {
|
||||
case 'series': {
|
||||
if (this.panel.xaxis.values.length === 0) {
|
||||
this.panel.xaxis.values = ['total'];
|
||||
return;
|
||||
}
|
||||
|
||||
var validOptions = this.getXAxisValueOptions({});
|
||||
var found = _.find(validOptions, {value: this.panel.xaxis.values[0]});
|
||||
if (!found) {
|
||||
this.panel.xaxis.values = ['total'];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDataFieldNames(dataList, onlyNumbers) {
|
||||
if (dataList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fields = [];
|
||||
var firstItem = dataList[0];
|
||||
if (firstItem.type === 'docs'){
|
||||
if (firstItem.datapoints.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fieldParts = [];
|
||||
|
||||
function getPropertiesRecursive(obj) {
|
||||
_.forEach(obj, (value, key) => {
|
||||
if (_.isObject(value)) {
|
||||
fieldParts.push(key);
|
||||
getPropertiesRecursive(value);
|
||||
} else {
|
||||
if (!onlyNumbers || _.isNumber(value)) {
|
||||
let field = fieldParts.concat(key).join('.');
|
||||
fields.push(field);
|
||||
}
|
||||
}
|
||||
});
|
||||
fieldParts.pop();
|
||||
}
|
||||
|
||||
getPropertiesRecursive(firstItem.datapoints[0]);
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
getXAxisValueOptions(options) {
|
||||
switch (this.panel.xaxis.mode) {
|
||||
case 'time': {
|
||||
return [];
|
||||
}
|
||||
case 'series': {
|
||||
return [
|
||||
{text: 'Avg', value: 'avg'},
|
||||
{text: 'Min', value: 'min'},
|
||||
{text: 'Max', value: 'min'},
|
||||
{text: 'Total', value: 'total'},
|
||||
{text: 'Count', value: 'count'},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pluckDeep(obj: any, property: string) {
|
||||
let propertyParts = property.split('.');
|
||||
let value = obj;
|
||||
for (let i = 0; i < propertyParts.length; ++i) {
|
||||
if (value[propertyParts[i]]) {
|
||||
value = value[propertyParts[i]];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,533 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'moment',
|
||||
'lodash',
|
||||
'app/core/utils/kbn',
|
||||
'./graph_tooltip',
|
||||
'./threshold_manager',
|
||||
'jquery.flot',
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent',
|
||||
'jquery.flot.fillbelow',
|
||||
'jquery.flot.crosshair',
|
||||
'./jquery.flot.events',
|
||||
],
|
||||
function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
var labelWidthCache = {};
|
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var ctrl = scope.ctrl;
|
||||
var dashboard = ctrl.dashboard;
|
||||
var panel = ctrl.panel;
|
||||
var data, annotations;
|
||||
var sortedSeries;
|
||||
var legendSideLastValue = null;
|
||||
var rootScope = scope.$root;
|
||||
var panelWidth = 0;
|
||||
var thresholdManager = new thresholdManExports.ThresholdManager(ctrl);
|
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) {
|
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(dashboard.sharedCrosshair) {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
|
||||
}
|
||||
}
|
||||
}, scope);
|
||||
|
||||
rootScope.onAppEvent('clearCrosshair', function() {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.clearCrosshair();
|
||||
}
|
||||
}, scope);
|
||||
|
||||
// Receive render events
|
||||
ctrl.events.on('render', function(renderData) {
|
||||
data = renderData || data;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
annotations = data.annotations || annotations;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function getLegendHeight(panelHeight) {
|
||||
if (!panel.legend.show || panel.legend.rightSide) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (panel.legend.alignAsTable) {
|
||||
var legendSeries = _.filter(data, function(series) {
|
||||
return series.hideFromLegend(panel.legend) === false;
|
||||
});
|
||||
var total = 23 + (21 * legendSeries.length);
|
||||
return Math.min(total, Math.floor(panelHeight/2));
|
||||
} else {
|
||||
return 26;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = ctrl.height - getLegendHeight(ctrl.height);
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
console.log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldAbortRender() {
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!setElementHeight()) { return true; }
|
||||
|
||||
if (panelWidth === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelWidth(text, elem) {
|
||||
var labelWidth = labelWidthCache[text];
|
||||
|
||||
if (!labelWidth) {
|
||||
labelWidth = labelWidthCache[text] = elem.width();
|
||||
}
|
||||
|
||||
return labelWidth;
|
||||
}
|
||||
|
||||
function drawHook(plot) {
|
||||
// Update legend values
|
||||
var yaxis = plot.getYAxes();
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
var axis = yaxis[series.yaxis - 1];
|
||||
var formater = kbn.valueFormats[panel.yaxes[series.yaxis - 1].format];
|
||||
|
||||
// decimal override
|
||||
if (_.isNumber(panel.decimals)) {
|
||||
series.updateLegendValues(formater, panel.decimals, null);
|
||||
} else {
|
||||
// auto decimals
|
||||
// legend and tooltip gets one more decimal precision
|
||||
// than graph legend ticks
|
||||
var tickDecimals = (axis.tickDecimals || -1) + 1;
|
||||
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
|
||||
}
|
||||
|
||||
if(!rootScope.$$phase) { scope.$digest(); }
|
||||
}
|
||||
|
||||
// add left axis labels
|
||||
if (panel.yaxes[0].label) {
|
||||
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
|
||||
.text(panel.yaxes[0].label)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
// add right axis labels
|
||||
if (panel.yaxes[1].label) {
|
||||
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
|
||||
.text(panel.yaxes[1].label)
|
||||
.appendTo(elem);
|
||||
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
thresholdManager.draw(plot);
|
||||
}
|
||||
|
||||
function processOffsetHook(plot, gridMargin) {
|
||||
var left = panel.yaxes[0];
|
||||
var right = panel.yaxes[1];
|
||||
if (left.show && left.label) { gridMargin.left = 20; }
|
||||
if (right.show && right.label) { gridMargin.right = 20; }
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
panelWidth = elem.width();
|
||||
|
||||
if (shouldAbortRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// give space to alert editing
|
||||
thresholdManager.prepare(elem, data);
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
hooks: {
|
||||
draw: [drawHook],
|
||||
processOffset: [processOffsetHook],
|
||||
},
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
stack: panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: translateFillOption(panel.fill),
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
barWidth: 1,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.points ? panel.pointradius : 2
|
||||
},
|
||||
shadowSize: 0
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {},
|
||||
grid: {
|
||||
minBorderMargin: 0,
|
||||
markings: [],
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8',
|
||||
margin: { left: 0, right: 0 },
|
||||
},
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
},
|
||||
crosshair: {
|
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
|
||||
|
||||
// if hidden remove points and disable stack
|
||||
if (ctrl.hiddenSeries[series.alias]) {
|
||||
series.data = [];
|
||||
series.stack = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length && data[0].stats.timeStep) {
|
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
|
||||
}
|
||||
|
||||
addTimeAxis(options);
|
||||
thresholdManager.addPlotOptions(options, panel);
|
||||
addAnnotations(options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
function callPlot(incrementRenderCounter) {
|
||||
try {
|
||||
$.plot(elem, sortedSeries, options);
|
||||
if (ctrl.renderError) {
|
||||
delete ctrl.error;
|
||||
delete ctrl.inspector;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('flotcharts error', e);
|
||||
ctrl.error = e.message || "Render Error";
|
||||
ctrl.renderError = true;
|
||||
ctrl.inspector = {error: e};
|
||||
}
|
||||
|
||||
if (incrementRenderCounter) {
|
||||
ctrl.renderingCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelayDraw(panel)) {
|
||||
// temp fix for legends on the side, need to render twice to get dimensions right
|
||||
callPlot(false);
|
||||
setTimeout(function() { callPlot(true); }, 50);
|
||||
legendSideLastValue = panel.legend.rightSide;
|
||||
}
|
||||
else {
|
||||
callPlot(true);
|
||||
}
|
||||
}
|
||||
|
||||
function translateFillOption(fill) {
|
||||
return fill === 0 ? 0.001 : fill/10;
|
||||
}
|
||||
|
||||
function shouldDelayDraw(panel) {
|
||||
if (panel.legend.rightSide) {
|
||||
return true;
|
||||
}
|
||||
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function addTimeAxis(options) {
|
||||
var ticks = panelWidth / 100;
|
||||
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
|
||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel.xaxis.show,
|
||||
mode: "time",
|
||||
min: min,
|
||||
max: max,
|
||||
label: "Datetime",
|
||||
ticks: ticks,
|
||||
timeformat: time_format(ticks, min, max),
|
||||
};
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
if(!annotations || annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var types = {};
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var item = annotations[i];
|
||||
|
||||
if (!types[item.source.name]) {
|
||||
types[item.source.name] = {
|
||||
color: item.source.iconColor,
|
||||
position: 'BOTTOM',
|
||||
markerSize: 5,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
options.events = {
|
||||
levels: _.keys(types).length + 1,
|
||||
data: annotations,
|
||||
types: types,
|
||||
};
|
||||
}
|
||||
|
||||
//Override min/max to provide more flexible autoscaling
|
||||
function autoscaleSpanOverride(yaxis, data, options) {
|
||||
var expr;
|
||||
if (yaxis.min != null && data != null) {
|
||||
expr = parseThresholdExpr(yaxis.min);
|
||||
options.min = autoscaleYAxisMin(expr, data.stats);
|
||||
}
|
||||
if (yaxis.max != null && data != null) {
|
||||
expr = parseThresholdExpr(yaxis.max);
|
||||
options.max = autoscaleYAxisMax(expr, data.stats);
|
||||
}
|
||||
}
|
||||
|
||||
function parseThresholdExpr(expr) {
|
||||
var match, operator, value, precision;
|
||||
expr = String(expr);
|
||||
match = expr.match(/\s*([<=>~]*)\s*(\-?\d+(\.\d+)?)/);
|
||||
if (match) {
|
||||
operator = match[1];
|
||||
value = parseFloat(match[2]);
|
||||
//Precision based on input
|
||||
precision = match[3] ? match[3].length - 1 : 0;
|
||||
return {
|
||||
operator: operator,
|
||||
value: value,
|
||||
precision: precision
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function autoscaleYAxisMax(expr, dataStats) {
|
||||
var operator = expr.operator,
|
||||
value = expr.value,
|
||||
precision = expr.precision;
|
||||
if (operator === ">") {
|
||||
return dataStats.max < value ? value : null;
|
||||
} else if (operator === "<") {
|
||||
return dataStats.max > value ? value : null;
|
||||
} else if (operator === "~") {
|
||||
return kbn.roundValue(dataStats.avg + value, precision);
|
||||
} else if (operator === "=") {
|
||||
return kbn.roundValue(dataStats.current + value, precision);
|
||||
} else if (!operator && !isNaN(value)) {
|
||||
return kbn.roundValue(value, precision);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function autoscaleYAxisMin(expr, dataStats) {
|
||||
var operator = expr.operator,
|
||||
value = expr.value,
|
||||
precision = expr.precision;
|
||||
if (operator === ">") {
|
||||
return dataStats.min < value ? value : null;
|
||||
} else if (operator === "<") {
|
||||
return dataStats.min > value ? value : null;
|
||||
} else if (operator === "~") {
|
||||
return kbn.roundValue(dataStats.avg - value, precision);
|
||||
} else if (operator === "=") {
|
||||
return kbn.roundValue(dataStats.current - value, precision);
|
||||
} else if (!operator && !isNaN(value)) {
|
||||
return kbn.roundValue(value, precision);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: panel.yaxes[0].show,
|
||||
min: panel.yaxes[0].min,
|
||||
index: 1,
|
||||
logBase: panel.yaxes[0].logBase || 1,
|
||||
max: panel.percentage && panel.stack ? 100 : panel.yaxes[0].max,
|
||||
};
|
||||
|
||||
autoscaleSpanOverride(panel.yaxes[0], data[0], defaults);
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.find(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.index = 2,
|
||||
secondY.show = panel.yaxes[1].show;
|
||||
secondY.logBase = panel.yaxes[1].logBase || 1,
|
||||
secondY.position = 'right';
|
||||
secondY.min = panel.yaxes[1].min;
|
||||
secondY.max = panel.percentage && panel.stack ? 100 : panel.yaxes[1].max;
|
||||
autoscaleSpanOverride(panel.yaxes[1], data[1], secondY);
|
||||
options.yaxes.push(secondY);
|
||||
|
||||
applyLogScale(options.yaxes[1], data);
|
||||
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format);
|
||||
}
|
||||
|
||||
applyLogScale(options.yaxes[0], data);
|
||||
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format);
|
||||
}
|
||||
|
||||
function applyLogScale(axis, data) {
|
||||
if (axis.logBase === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var series, i;
|
||||
var max = axis.max;
|
||||
|
||||
if (max === null) {
|
||||
for (i = 0; i < data.length; i++) {
|
||||
series = data[i];
|
||||
if (series.yaxis === axis.index) {
|
||||
if (max < series.stats.max) {
|
||||
max = series.stats.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max === void 0) {
|
||||
max = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
axis.min = axis.min !== null ? axis.min : 0;
|
||||
axis.ticks = [0, 1];
|
||||
var nextTick = 1;
|
||||
|
||||
while (true) {
|
||||
nextTick = nextTick * axis.logBase;
|
||||
axis.ticks.push(nextTick);
|
||||
if (nextTick > max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (axis.logBase === 10) {
|
||||
axis.transform = function(v) { return Math.log(v+0.1); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); };
|
||||
} else {
|
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); };
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
axis.tickFormatter = function(val, axis) {
|
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
|
||||
};
|
||||
}
|
||||
|
||||
function time_format(ticks, min, max) {
|
||||
if (min && max && ticks) {
|
||||
var range = max - min;
|
||||
var secPerTick = (range/ticks) / 1000;
|
||||
var oneDay = 86400000;
|
||||
var oneYear = 31536000000;
|
||||
|
||||
if (secPerTick <= 45) {
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
if (secPerTick <= 7200 || range <= oneDay) {
|
||||
return "%H:%M";
|
||||
}
|
||||
if (secPerTick <= 80000) {
|
||||
return "%m/%d %H:%M";
|
||||
}
|
||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||
return "%m/%d";
|
||||
}
|
||||
return "%Y-%m";
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() {
|
||||
return sortedSeries;
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from),
|
||||
to : moment.utc(ranges.xaxis.to),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
521
public/app/plugins/panel/graph/graph.ts
Executable file
521
public/app/plugins/panel/graph/graph.ts
Executable file
@@ -0,0 +1,521 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import 'jquery.flot';
|
||||
import 'jquery.flot.selection';
|
||||
import 'jquery.flot.time';
|
||||
import 'jquery.flot.stack';
|
||||
import 'jquery.flot.stackpercent';
|
||||
import 'jquery.flot.fillbelow';
|
||||
import 'jquery.flot.crosshair';
|
||||
import './jquery.flot.events';
|
||||
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import GraphTooltip from './graph_tooltip';
|
||||
import {ThresholdManager} from './threshold_manager';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
var labelWidthCache = {};
|
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '',
|
||||
link: function(scope, elem) {
|
||||
var ctrl = scope.ctrl;
|
||||
var dashboard = ctrl.dashboard;
|
||||
var panel = ctrl.panel;
|
||||
var data, annotations;
|
||||
var sortedSeries;
|
||||
var legendSideLastValue = null;
|
||||
var rootScope = scope.$root;
|
||||
var panelWidth = 0;
|
||||
var thresholdManager = new ThresholdManager(ctrl);
|
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) {
|
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dashboard.sharedCrosshair) {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
|
||||
}
|
||||
}
|
||||
}, scope);
|
||||
|
||||
rootScope.onAppEvent('clearCrosshair', function() {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.clearCrosshair();
|
||||
}
|
||||
}, scope);
|
||||
|
||||
// Receive render events
|
||||
ctrl.events.on('render', function(renderData) {
|
||||
data = renderData || data;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
annotations = data.annotations || annotations;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function getLegendHeight(panelHeight) {
|
||||
if (!panel.legend.show || panel.legend.rightSide) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (panel.legend.alignAsTable) {
|
||||
var legendSeries = _.filter(data, function(series) {
|
||||
return series.hideFromLegend(panel.legend) === false;
|
||||
});
|
||||
var total = 23 + (21 * legendSeries.length);
|
||||
return Math.min(total, Math.floor(panelHeight/2));
|
||||
} else {
|
||||
return 26;
|
||||
}
|
||||
}
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = ctrl.height - getLegendHeight(ctrl.height);
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
return true;
|
||||
} catch (e) { // IE throws errors sometimes
|
||||
console.log(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldAbortRender() {
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!setElementHeight()) { return true; }
|
||||
|
||||
if (panelWidth === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelWidth(text, elem) {
|
||||
var labelWidth = labelWidthCache[text];
|
||||
|
||||
if (!labelWidth) {
|
||||
labelWidth = labelWidthCache[text] = elem.width();
|
||||
}
|
||||
|
||||
return labelWidth;
|
||||
}
|
||||
|
||||
function drawHook(plot) {
|
||||
// Update legend values
|
||||
var yaxis = plot.getYAxes();
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
var axis = yaxis[series.yaxis - 1];
|
||||
var formater = kbn.valueFormats[panel.yaxes[series.yaxis - 1].format];
|
||||
|
||||
// decimal override
|
||||
if (_.isNumber(panel.decimals)) {
|
||||
series.updateLegendValues(formater, panel.decimals, null);
|
||||
} else {
|
||||
// auto decimals
|
||||
// legend and tooltip gets one more decimal precision
|
||||
// than graph legend ticks
|
||||
var tickDecimals = (axis.tickDecimals || -1) + 1;
|
||||
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
|
||||
}
|
||||
|
||||
if (!rootScope.$$phase) { scope.$digest(); }
|
||||
}
|
||||
|
||||
// add left axis labels
|
||||
if (panel.yaxes[0].label) {
|
||||
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
|
||||
.text(panel.yaxes[0].label)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
// add right axis labels
|
||||
if (panel.yaxes[1].label) {
|
||||
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
|
||||
.text(panel.yaxes[1].label)
|
||||
.appendTo(elem);
|
||||
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
thresholdManager.draw(plot);
|
||||
}
|
||||
|
||||
function processOffsetHook(plot, gridMargin) {
|
||||
var left = panel.yaxes[0];
|
||||
var right = panel.yaxes[1];
|
||||
if (left.show && left.label) { gridMargin.left = 20; }
|
||||
if (right.show && right.label) { gridMargin.right = 20; }
|
||||
|
||||
// apply y-axis min/max options
|
||||
var yaxis = plot.getYAxes();
|
||||
for (var i = 0; i < yaxis.length; i++) {
|
||||
var axis = yaxis[i];
|
||||
var panelOptions = panel.yaxes[i];
|
||||
axis.options.max = panelOptions.max;
|
||||
axis.options.min = panelOptions.min;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
panelWidth = elem.width();
|
||||
|
||||
if (shouldAbortRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// give space to alert editing
|
||||
thresholdManager.prepare(elem, data);
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options: any = {
|
||||
hooks: {
|
||||
draw: [drawHook],
|
||||
processOffset: [processOffsetHook],
|
||||
},
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
stack: panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: translateFillOption(panel.fill),
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
barWidth: 1,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.points ? panel.pointradius : 2
|
||||
},
|
||||
shadowSize: 0
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {},
|
||||
grid: {
|
||||
minBorderMargin: 0,
|
||||
markings: [],
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8',
|
||||
margin: { left: 0, right: 0 },
|
||||
},
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
},
|
||||
crosshair: {
|
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
|
||||
|
||||
// if hidden remove points and disable stack
|
||||
if (ctrl.hiddenSeries[series.alias]) {
|
||||
series.data = [];
|
||||
series.stack = false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (panel.xaxis.mode) {
|
||||
case 'series': {
|
||||
options.series.bars.barWidth = 0.7;
|
||||
options.series.bars.align = 'center';
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]];
|
||||
}
|
||||
|
||||
addXSeriesAxis(options);
|
||||
break;
|
||||
}
|
||||
case 'table': {
|
||||
options.series.bars.barWidth = 0.7;
|
||||
options.series.bars.align = 'center';
|
||||
addXTableAxis(options);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (data.length && data[0].stats.timeStep) {
|
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
|
||||
}
|
||||
addTimeAxis(options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
thresholdManager.addPlotOptions(options, panel);
|
||||
addAnnotations(options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
function callPlot(incrementRenderCounter) {
|
||||
try {
|
||||
$.plot(elem, sortedSeries, options);
|
||||
if (ctrl.renderError) {
|
||||
delete ctrl.error;
|
||||
delete ctrl.inspector;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('flotcharts error', e);
|
||||
ctrl.error = e.message || "Render Error";
|
||||
ctrl.renderError = true;
|
||||
ctrl.inspector = {error: e};
|
||||
}
|
||||
|
||||
if (incrementRenderCounter) {
|
||||
ctrl.renderingCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelayDraw(panel)) {
|
||||
// temp fix for legends on the side, need to render twice to get dimensions right
|
||||
callPlot(false);
|
||||
setTimeout(function() { callPlot(true); }, 50);
|
||||
legendSideLastValue = panel.legend.rightSide;
|
||||
} else {
|
||||
callPlot(true);
|
||||
}
|
||||
}
|
||||
|
||||
function translateFillOption(fill) {
|
||||
return fill === 0 ? 0.001 : fill/10;
|
||||
}
|
||||
|
||||
function shouldDelayDraw(panel) {
|
||||
if (panel.legend.rightSide) {
|
||||
return true;
|
||||
}
|
||||
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function addTimeAxis(options) {
|
||||
var ticks = panelWidth / 100;
|
||||
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
|
||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel.xaxis.show,
|
||||
mode: "time",
|
||||
min: min,
|
||||
max: max,
|
||||
label: "Datetime",
|
||||
ticks: ticks,
|
||||
timeformat: time_format(ticks, min, max),
|
||||
};
|
||||
}
|
||||
|
||||
function addXSeriesAxis(options) {
|
||||
var ticks = _.map(data, function(series, index) {
|
||||
return [index + 1, series.alias];
|
||||
});
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel.xaxis.show,
|
||||
mode: null,
|
||||
min: 0,
|
||||
max: ticks.length + 1,
|
||||
label: "Datetime",
|
||||
ticks: ticks
|
||||
};
|
||||
}
|
||||
|
||||
function addXTableAxis(options) {
|
||||
var ticks = _.map(data, function(series, seriesIndex) {
|
||||
return _.map(series.datapoints, function(point, pointIndex) {
|
||||
var tickIndex = seriesIndex * series.datapoints.length + pointIndex;
|
||||
return [tickIndex + 1, point[1]];
|
||||
});
|
||||
});
|
||||
ticks = _.flatten(ticks, true);
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel.xaxis.show,
|
||||
mode: null,
|
||||
min: 0,
|
||||
max: ticks.length + 1,
|
||||
label: "Datetime",
|
||||
ticks: ticks
|
||||
};
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
if (!annotations || annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var types = {};
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var item = annotations[i];
|
||||
|
||||
if (!types[item.source.name]) {
|
||||
types[item.source.name] = {
|
||||
color: item.source.iconColor,
|
||||
position: 'BOTTOM',
|
||||
markerSize: 5,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
options.events = {
|
||||
levels: _.keys(types).length + 1,
|
||||
data: annotations,
|
||||
types: types,
|
||||
};
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: panel.yaxes[0].show,
|
||||
index: 1,
|
||||
logBase: panel.yaxes[0].logBase || 1,
|
||||
max: 100, // correct later
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.find(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.index = 2;
|
||||
secondY.show = panel.yaxes[1].show;
|
||||
secondY.logBase = panel.yaxes[1].logBase || 1;
|
||||
secondY.position = 'right';
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format);
|
||||
}
|
||||
|
||||
applyLogScale(options.yaxes[0], data);
|
||||
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format);
|
||||
}
|
||||
|
||||
function applyLogScale(axis, data) {
|
||||
if (axis.logBase === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var series, i;
|
||||
var max = axis.max;
|
||||
|
||||
if (max === null) {
|
||||
for (i = 0; i < data.length; i++) {
|
||||
series = data[i];
|
||||
if (series.yaxis === axis.index) {
|
||||
if (max < series.stats.max) {
|
||||
max = series.stats.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (max === void 0) {
|
||||
max = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
axis.min = axis.min !== null ? axis.min : 0;
|
||||
axis.ticks = [0, 1];
|
||||
var nextTick = 1;
|
||||
|
||||
while (true) {
|
||||
nextTick = nextTick * axis.logBase;
|
||||
axis.ticks.push(nextTick);
|
||||
if (nextTick > max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (axis.logBase === 10) {
|
||||
axis.transform = function(v) { return Math.log(v+0.1); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); };
|
||||
} else {
|
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); };
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
axis.tickFormatter = function(val, axis) {
|
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
|
||||
};
|
||||
}
|
||||
|
||||
function time_format(ticks, min, max) {
|
||||
if (min && max && ticks) {
|
||||
var range = max - min;
|
||||
var secPerTick = (range/ticks) / 1000;
|
||||
var oneDay = 86400000;
|
||||
var oneYear = 31536000000;
|
||||
|
||||
if (secPerTick <= 45) {
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
if (secPerTick <= 7200 || range <= oneDay) {
|
||||
return "%H:%M";
|
||||
}
|
||||
if (secPerTick <= 80000) {
|
||||
return "%m/%d %H:%M";
|
||||
}
|
||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||
return "%m/%d";
|
||||
}
|
||||
return "%Y-%m";
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() {
|
||||
return sortedSeries;
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from),
|
||||
to : moment.utc(ranges.xaxis.to),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -121,20 +121,20 @@ function ($, _) {
|
||||
var seriesList = getSeriesFn();
|
||||
var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
|
||||
|
||||
if (panel.tooltip.msResolution) {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
} else {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
}
|
||||
|
||||
if (dashboard.sharedCrosshair) {
|
||||
ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
|
||||
ctrl.publishAppEvent('setCrosshair', {pos: pos, scope: scope});
|
||||
}
|
||||
|
||||
if (seriesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (seriesList[0].hasMsResolution) {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
} else {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
}
|
||||
|
||||
if (panel.tooltip.shared) {
|
||||
plot.unhighlight();
|
||||
|
||||
|
||||
@@ -8,26 +8,26 @@ import './thresholds_form';
|
||||
import template from './template';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import _ from 'lodash';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import config from 'app/core/config';
|
||||
import * as fileExport from 'app/core/utils/file_export';
|
||||
import {MetricsPanelCtrl, alertTab} from 'app/plugins/sdk';
|
||||
import {DataProcessor} from './data_processor';
|
||||
import {axesEditorComponent} from './axes_editor';
|
||||
|
||||
class GraphCtrl extends MetricsPanelCtrl {
|
||||
static template = template;
|
||||
|
||||
hiddenSeries: any = {};
|
||||
seriesList: any = [];
|
||||
logScales: any;
|
||||
unitFormats: any;
|
||||
dataList: any = [];
|
||||
annotationsPromise: any;
|
||||
datapointsCount: number;
|
||||
datapointsOutside: boolean;
|
||||
datapointsWarning: boolean;
|
||||
colors: any = [];
|
||||
subTabIndex: number;
|
||||
processor: DataProcessor;
|
||||
|
||||
panelDefaults = {
|
||||
// datasource name, null = default datasource
|
||||
@@ -53,7 +53,10 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
],
|
||||
xaxis: {
|
||||
show: true
|
||||
show: true,
|
||||
mode: 'time',
|
||||
name: null,
|
||||
values: [],
|
||||
},
|
||||
// show/hide lines
|
||||
lines : true,
|
||||
@@ -111,8 +114,9 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
_.defaults(this.panel, this.panelDefaults);
|
||||
_.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
|
||||
_.defaults(this.panel.legend, this.panelDefaults.legend);
|
||||
_.defaults(this.panel.xaxis, this.panelDefaults.xaxis);
|
||||
|
||||
this.colors = $scope.$root.colors;
|
||||
this.processor = new DataProcessor(this.panel);
|
||||
|
||||
this.events.on('render', this.onRender.bind(this));
|
||||
this.events.on('data-received', this.onDataReceived.bind(this));
|
||||
@@ -123,23 +127,13 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
onInitEditMode() {
|
||||
this.addEditorTab('Axes', 'public/app/plugins/panel/graph/tab_axes.html', 2);
|
||||
this.addEditorTab('Axes', axesEditorComponent, 2);
|
||||
this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3);
|
||||
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
|
||||
|
||||
if (config.alertingEnabled) {
|
||||
this.addEditorTab('Alert', alertTab, 5);
|
||||
}
|
||||
|
||||
this.logScales = {
|
||||
'linear': 1,
|
||||
'log (base 2)': 2,
|
||||
'log (base 10)': 10,
|
||||
'log (base 32)': 32,
|
||||
'log (base 1024)': 1024
|
||||
};
|
||||
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
this.subTabIndex = 0;
|
||||
}
|
||||
|
||||
@@ -149,11 +143,6 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
actions.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'});
|
||||
}
|
||||
|
||||
setUnitFormat(axis, subItem) {
|
||||
axis.format = subItem.value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
issueQueries(datasource) {
|
||||
this.annotationsPromise = this.annotationsSrv.getAnnotations({
|
||||
dashboard: this.dashboard,
|
||||
@@ -182,11 +171,20 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
onDataReceived(dataList) {
|
||||
this.datapointsWarning = false;
|
||||
this.datapointsCount = 0;
|
||||
|
||||
this.dataList = dataList;
|
||||
this.seriesList = this.processor.getSeriesList({dataList: dataList, range: this.range});
|
||||
|
||||
this.datapointsCount = this.seriesList.reduce((prev, series) => {
|
||||
return prev + series.datapoints.length;
|
||||
}, 0);
|
||||
|
||||
this.datapointsOutside = false;
|
||||
this.seriesList = dataList.map(this.seriesHandler.bind(this));
|
||||
this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
|
||||
for (let series of this.seriesList) {
|
||||
if (series.isOutsideRange) {
|
||||
this.datapointsOutside = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.annotationsPromise.then(annotations => {
|
||||
this.loading = false;
|
||||
@@ -198,34 +196,6 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
seriesHandler(seriesData, index) {
|
||||
var datapoints = seriesData.datapoints;
|
||||
var alias = seriesData.target;
|
||||
var colorIndex = index % this.colors.length;
|
||||
var color = this.panel.aliasColors[alias] || this.colors[colorIndex];
|
||||
|
||||
var series = new TimeSeries({
|
||||
datapoints: datapoints,
|
||||
alias: alias,
|
||||
color: color,
|
||||
unit: seriesData.unit,
|
||||
});
|
||||
|
||||
if (datapoints && datapoints.length > 0) {
|
||||
var last = moment.utc(datapoints[datapoints.length - 1][1]);
|
||||
var from = moment.utc(this.range.from);
|
||||
if (last - from < -10000) {
|
||||
this.datapointsOutside = true;
|
||||
}
|
||||
|
||||
this.datapointsCount += datapoints.length;
|
||||
this.panel.tooltip.msResolution = this.panel.tooltip.msResolution || series.isMsResolutionNeeded();
|
||||
}
|
||||
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
onRender() {
|
||||
if (!this.seriesList) { return; }
|
||||
|
||||
@@ -309,13 +279,11 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
this.render();
|
||||
}
|
||||
|
||||
// Called from panel menu
|
||||
toggleLegend() {
|
||||
this.panel.legend.show = !this.panel.legend.show;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
||||
legendValuesOptionChanged() {
|
||||
var legend = this.panel.legend;
|
||||
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
|
||||
|
||||
64
public/app/plugins/panel/graph/specs/data_processor_specs.ts
Normal file
64
public/app/plugins/panel/graph/specs/data_processor_specs.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
///<reference path="../../../../headers/common.d.ts" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||
|
||||
import {DataProcessor} from '../data_processor';
|
||||
|
||||
describe('Graph DataProcessor', function() {
|
||||
var panel: any = {
|
||||
xaxis: {}
|
||||
};
|
||||
var processor = new DataProcessor(panel);
|
||||
var seriesList;
|
||||
|
||||
describe('Given default xaxis options and query that returns docs', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
panel.xaxis.mode = 'time';
|
||||
panel.xaxis.name = 'hostname';
|
||||
panel.xaxis.values = [];
|
||||
|
||||
seriesList = processor.getSeriesList({
|
||||
dataList: [
|
||||
{
|
||||
type: 'docs',
|
||||
datapoints: [{hostname: "server1", avg: 10}]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('Should automatically set xaxis mode to field', () => {
|
||||
expect(panel.xaxis.mode).to.be('field');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('getDataFieldNames(', () => {
|
||||
var dataList = [{
|
||||
type: 'docs', datapoints: [
|
||||
{
|
||||
hostname: "server1",
|
||||
valueField: 11,
|
||||
nested: {
|
||||
prop1: 'server2', value2: 23}
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
it('Should return all field names', () => {
|
||||
var fields = processor.getDataFieldNames(dataList, false);
|
||||
expect(fields).to.contain('hostname');
|
||||
expect(fields).to.contain('valueField');
|
||||
expect(fields).to.contain('nested.prop1');
|
||||
expect(fields).to.contain('nested.value2');
|
||||
});
|
||||
|
||||
it('Should return all number fields', () => {
|
||||
var fields = processor.getDataFieldNames(dataList, true);
|
||||
expect(fields).to.contain('valueField');
|
||||
expect(fields).to.contain('nested.value2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
|
||||
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import {GraphCtrl} from '../module';
|
||||
import helpers from '../../../../../test/specs/helpers';
|
||||
|
||||
@@ -19,64 +20,53 @@ describe('GraphCtrl', function() {
|
||||
ctx.ctrl.updateTimeRange();
|
||||
});
|
||||
|
||||
describe('msResolution with second resolution timestamps', function() {
|
||||
describe('when time series are outside range', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
var data = [
|
||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]}
|
||||
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||
];
|
||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||
|
||||
ctx.ctrl.range = {from: moment().valueOf(), to: moment().valueOf()};
|
||||
ctx.ctrl.onDataReceived(data);
|
||||
});
|
||||
|
||||
it('should not show millisecond resolution tooltip', function() {
|
||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
|
||||
it('should set datapointsOutside', function() {
|
||||
expect(ctx.ctrl.datapointsOutside).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msResolution with millisecond resolution timestamps', function() {
|
||||
describe('when time series are inside range', function() {
|
||||
beforeEach(function() {
|
||||
var range = {
|
||||
from: moment().subtract(1, 'days').valueOf(),
|
||||
to: moment().valueOf()
|
||||
};
|
||||
|
||||
var data = [
|
||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]}
|
||||
{target: 'test.cpu1', datapoints: [[45, range.from + 1000], [60, range.from + 10000]]},
|
||||
];
|
||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||
|
||||
ctx.ctrl.range = range;
|
||||
ctx.ctrl.onDataReceived(data);
|
||||
});
|
||||
|
||||
it('should show millisecond resolution tooltip', function() {
|
||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(true);
|
||||
it('should set datapointsOutside', function() {
|
||||
expect(ctx.ctrl.datapointsOutside).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
|
||||
describe('datapointsCount given 2 series', function() {
|
||||
beforeEach(function() {
|
||||
var data = [
|
||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]}
|
||||
{target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||
{target: 'test.cpu2', datapoints: [[45, 1234567890]]},
|
||||
];
|
||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||
ctx.ctrl.onDataReceived(data);
|
||||
});
|
||||
|
||||
it('should not show millisecond resolution tooltip', function() {
|
||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msResolution with millisecond resolution timestamps in one of the series', function() {
|
||||
beforeEach(function() {
|
||||
var data = [
|
||||
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||
{ target: 'test.cpu2', datapoints: [[55, 1236547890010], [90, 1234456709000]]},
|
||||
{ target: 'test.cpu3', datapoints: [[65, 1236547890000], [120, 1234456709000]]}
|
||||
];
|
||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||
ctx.ctrl.onDataReceived(data);
|
||||
});
|
||||
|
||||
it('should show millisecond resolution tooltip', function() {
|
||||
expect(ctx.ctrl.panel.tooltip.msResolution).to.be(true);
|
||||
it('should set datapointsCount to sum of datapoints', function() {
|
||||
expect(ctx.ctrl.datapointsCount).to.be(3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -219,145 +219,145 @@ describe('grafanaGraph', function() {
|
||||
|
||||
}, 10);
|
||||
|
||||
graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) {
|
||||
describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '<100';
|
||||
ctrl.panel.yaxes[0].max = '>200';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to 100 and max to 200', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(100);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(200);
|
||||
});
|
||||
});
|
||||
describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '<100';
|
||||
ctrl.panel.yaxes[0].max = '>200';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[99,10],[201,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to auto and max to auto', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(null);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(null);
|
||||
});
|
||||
});
|
||||
describe('and Y-Min is =10.5 and Y-Max is =10.5', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '=10.5';
|
||||
ctrl.panel.yaxes[0].max = '=10.5';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[100,10],[120,20], [110,30]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to last value + 10.5 and max to last value + 10.5', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(99.5);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(120.5);
|
||||
});
|
||||
});
|
||||
describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '~10.5';
|
||||
ctrl.panel.yaxes[0].max = '~10.5';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[102,10],[104,20], [110,30]], //Also checks precision
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to average value + 10.5 and max to average value + 10.5', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(94.8);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(115.8);
|
||||
});
|
||||
});
|
||||
});
|
||||
graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) {
|
||||
describe('and Y-Min is 100 and Y-Max is 200', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '100';
|
||||
ctrl.panel.yaxes[0].max = '200';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to 100 and max to 200', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(100);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(200);
|
||||
});
|
||||
});
|
||||
describe('and Y-Min is 0 and Y-Max is 0', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '0';
|
||||
ctrl.panel.yaxes[0].max = '0';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to 0 and max to 0', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(0);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(0);
|
||||
});
|
||||
});
|
||||
describe('and negative values used', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = '-10';
|
||||
ctrl.panel.yaxes[0].max = '-13.14';
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min and max to negative', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(-10);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14);
|
||||
});
|
||||
});
|
||||
});
|
||||
graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) {
|
||||
describe('and Y-Min is 0 and Y-Max is 100', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = 0;
|
||||
ctrl.panel.yaxes[0].max = 100;
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to 0 and max to 100', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(0);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(100);
|
||||
});
|
||||
});
|
||||
describe('and Y-Min is -100 and Y-Max is -10.5', function() {
|
||||
ctx.setup(function(ctrl, data) {
|
||||
ctrl.panel.yaxes[0].min = -100;
|
||||
ctrl.panel.yaxes[0].max = -10.5;
|
||||
data[0] = new TimeSeries({
|
||||
datapoints: [[120,10],[160,20]],
|
||||
alias: 'series1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set min to -100 and max to -10.5', function() {
|
||||
expect(ctx.plotOptions.yaxes[0].min).to.be(-100);
|
||||
expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
// graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) {
|
||||
// describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '<100';
|
||||
// ctrl.panel.yaxes[0].max = '>200';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to 100 and max to 200', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
|
||||
// });
|
||||
// });
|
||||
// describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '<100';
|
||||
// ctrl.panel.yaxes[0].max = '>200';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[99,10],[201,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to auto and max to auto', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(null);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(null);
|
||||
// });
|
||||
// });
|
||||
// describe('and Y-Min is =10.5 and Y-Max is =10.5', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '=10.5';
|
||||
// ctrl.panel.yaxes[0].max = '=10.5';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[100,10],[120,20], [110,30]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to last value + 10.5 and max to last value + 10.5', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(99.5);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(120.5);
|
||||
// });
|
||||
// });
|
||||
// describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '~10.5';
|
||||
// ctrl.panel.yaxes[0].max = '~10.5';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[102,10],[104,20], [110,30]], //Also checks precision
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to average value + 10.5 and max to average value + 10.5', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(94.8);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(115.8);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) {
|
||||
// describe('and Y-Min is 100 and Y-Max is 200', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '100';
|
||||
// ctrl.panel.yaxes[0].max = '200';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to 100 and max to 200', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
|
||||
// });
|
||||
// });
|
||||
// describe('and Y-Min is 0 and Y-Max is 0', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '0';
|
||||
// ctrl.panel.yaxes[0].max = '0';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to 0 and max to 0', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(0);
|
||||
// });
|
||||
// });
|
||||
// describe('and negative values used', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = '-10';
|
||||
// ctrl.panel.yaxes[0].max = '-13.14';
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min and max to negative', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(-10);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) {
|
||||
// describe('and Y-Min is 0 and Y-Max is 100', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = 0;
|
||||
// ctrl.panel.yaxes[0].max = 100;
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to 0 and max to 100', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(100);
|
||||
// });
|
||||
// });
|
||||
// describe('and Y-Min is -100 and Y-Max is -10.5', function() {
|
||||
// ctx.setup(function(ctrl, data) {
|
||||
// ctrl.panel.yaxes[0].min = -100;
|
||||
// ctrl.panel.yaxes[0].max = -10.5;
|
||||
// data[0] = new TimeSeries({
|
||||
// datapoints: [[120,10],[160,20]],
|
||||
// alias: 'series1',
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should set min to -100 and max to -10.5', function() {
|
||||
// expect(ctx.plotOptions.yaxes[0].min).to.be(-100);
|
||||
// expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -2,11 +2,14 @@ var template = `
|
||||
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
|
||||
<div class="graph-canvas-wrapper">
|
||||
|
||||
<div ng-if="datapointsWarning" class="datapoints-warning">
|
||||
<span class="small" ng-show="!datapointsCount">
|
||||
<div class="datapoints-warning" ng-show="ctrl.datapointsCount===0">
|
||||
<span class="small" >
|
||||
No datapoints <tip>No datapoints returned from metric query</tip>
|
||||
</span>
|
||||
<span class="small" ng-show="datapointsOutside">
|
||||
</div>
|
||||
|
||||
<div class="datapoints-warning" ng-show="ctrl.datapointsOutside">
|
||||
<span class="small">
|
||||
Datapoints outside time range
|
||||
<tip>Can be caused by timezone mismatch between browser and graphite server</tip>
|
||||
</span>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-dashlist-panel.svg",
|
||||
"large": "img/icn-dashlist-panel.svg"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<grafana-panel-table-editor>
|
||||
</grafana-panel-table-editor>
|
||||
@@ -135,7 +135,7 @@ $gf-form-margin: 0.25rem;
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
right: $input-padding-x/2;
|
||||
right: $input-padding-x;
|
||||
background-color: transparent;
|
||||
color: $input-color;
|
||||
font: normal normal normal $font-size-sm/1 FontAwesome;
|
||||
|
||||
@@ -56,6 +56,38 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('When checking if ms resolution is needed', function() {
|
||||
describe('msResolution with second resolution timestamps', function() {
|
||||
beforeEach(function() {
|
||||
series = new TimeSeries({datapoints: [[45, 1234567890], [60, 1234567899]]});
|
||||
});
|
||||
|
||||
it('should set hasMsResolution to false', function() {
|
||||
expect(series.hasMsResolution).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msResolution with millisecond resolution timestamps', function() {
|
||||
beforeEach(function() {
|
||||
series = new TimeSeries({datapoints: [[55, 1236547890001], [90, 1234456709000]]});
|
||||
});
|
||||
|
||||
it('should show millisecond resolution tooltip', function() {
|
||||
expect(series.hasMsResolution).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
|
||||
beforeEach(function() {
|
||||
series = new TimeSeries({datapoints: [[45, 1234567890000], [60, 1234567899000]]});
|
||||
});
|
||||
|
||||
it('should not show millisecond resolution tooltip', function() {
|
||||
expect(series.hasMsResolution).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('can detect if series contains ms precision', function() {
|
||||
var fakedata;
|
||||
|
||||
|
||||
@@ -132,62 +132,64 @@ define([
|
||||
describe('calculateInterval', function() {
|
||||
it('1h 100 resultion', function() {
|
||||
var range = { from: dateMath.parse('now-1h'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 100, null);
|
||||
expect(str).to.be('30s');
|
||||
var res = kbn.calculateInterval(range, 100, null);
|
||||
expect(res.interval).to.be('30s');
|
||||
});
|
||||
|
||||
it('10m 1600 resolution', function() {
|
||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 1600, null);
|
||||
expect(str).to.be('500ms');
|
||||
var res = kbn.calculateInterval(range, 1600, null);
|
||||
expect(res.interval).to.be('500ms');
|
||||
expect(res.intervalMs).to.be(500);
|
||||
});
|
||||
|
||||
it('fixed user interval', function() {
|
||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 1600, '10s');
|
||||
expect(str).to.be('10s');
|
||||
var res = kbn.calculateInterval(range, 1600, '10s');
|
||||
expect(res.interval).to.be('10s');
|
||||
expect(res.intervalMs).to.be(10000);
|
||||
});
|
||||
|
||||
it('short time range and user low limit', function() {
|
||||
var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 1600, '>10s');
|
||||
expect(str).to.be('10s');
|
||||
var res = kbn.calculateInterval(range, 1600, '>10s');
|
||||
expect(res.interval).to.be('10s');
|
||||
});
|
||||
|
||||
it('large time range and user low limit', function() {
|
||||
var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 1000, '>10s');
|
||||
expect(str).to.be('20m');
|
||||
var range = {from: dateMath.parse('now-14d'), to: dateMath.parse('now')};
|
||||
var res = kbn.calculateInterval(range, 1000, '>10s');
|
||||
expect(res.interval).to.be('20m');
|
||||
});
|
||||
|
||||
|
||||
it('10s 900 resolution and user low limit in ms', function() {
|
||||
var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
|
||||
var str = kbn.calculateInterval(range, 900, '>15ms');
|
||||
expect(str).to.be('15ms');
|
||||
var res = kbn.calculateInterval(range, 900, '>15ms');
|
||||
expect(res.interval).to.be('15ms');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hex', function() {
|
||||
it('positive integer', function() {
|
||||
var str = kbn.valueFormats.hex(100, 0);
|
||||
expect(str).to.be('64');
|
||||
});
|
||||
it('negative integer', function() {
|
||||
var str = kbn.valueFormats.hex(-100, 0);
|
||||
expect(str).to.be('-64');
|
||||
});
|
||||
it('null', function() {
|
||||
var str = kbn.valueFormats.hex(null, 0);
|
||||
expect(str).to.be('');
|
||||
});
|
||||
it('positive float', function() {
|
||||
var str = kbn.valueFormats.hex(50.52, 1);
|
||||
expect(str).to.be('32.8');
|
||||
});
|
||||
it('negative float', function() {
|
||||
var str = kbn.valueFormats.hex(-50.333, 2);
|
||||
expect(str).to.be('-32.547AE147AE14');
|
||||
});
|
||||
it('positive integer', function() {
|
||||
var str = kbn.valueFormats.hex(100, 0);
|
||||
expect(str).to.be('64');
|
||||
});
|
||||
it('negative integer', function() {
|
||||
var str = kbn.valueFormats.hex(-100, 0);
|
||||
expect(str).to.be('-64');
|
||||
});
|
||||
it('null', function() {
|
||||
var str = kbn.valueFormats.hex(null, 0);
|
||||
expect(str).to.be('');
|
||||
});
|
||||
it('positive float', function() {
|
||||
var str = kbn.valueFormats.hex(50.52, 1);
|
||||
expect(str).to.be('32.8');
|
||||
});
|
||||
it('negative float', function() {
|
||||
var str = kbn.valueFormats.hex(-50.333, 2);
|
||||
expect(str).to.be('-32.547AE147AE14');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hex 0x', function() {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
define([
|
||||
'../mocks/dashboard-mock',
|
||||
'./helpers',
|
||||
'app/features/templating/templateValuesSrv'
|
||||
], function(dashboardMock, helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('templateValuesSrv', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', '$location']));
|
||||
beforeEach(ctx.createService('templateValuesSrv'));
|
||||
|
||||
describe('when template variable is present in url', function() {
|
||||
describe('and setting simple variable', function() {
|
||||
var variable = {
|
||||
name: 'apps',
|
||||
current: {text: "test", value: "test"},
|
||||
options: [{text: "test", value: "test"}]
|
||||
};
|
||||
|
||||
beforeEach(function(done) {
|
||||
var dashboard = { templating: { list: [variable] } };
|
||||
var urlParams = {};
|
||||
urlParams["var-apps"] = "new";
|
||||
ctx.$location.search = sinon.stub().returns(urlParams);
|
||||
ctx.service.init(dashboard).then(function() { done(); });
|
||||
ctx.$rootScope.$digest();
|
||||
});
|
||||
|
||||
it('should update current value', function() {
|
||||
expect(variable.current.value).to.be("new");
|
||||
expect(variable.current.text).to.be("new");
|
||||
});
|
||||
});
|
||||
|
||||
// describe('and setting adhoc variable', function() {
|
||||
// var variable = {name: 'filters', type: 'adhoc'};
|
||||
//
|
||||
// beforeEach(function(done) {
|
||||
// var dashboard = { templating: { list: [variable] } };
|
||||
// var urlParams = {};
|
||||
// urlParams["var-filters"] = "hostname|gt|server2";
|
||||
// ctx.$location.search = sinon.stub().returns(urlParams);
|
||||
// ctx.service.init(dashboard).then(function() { done(); });
|
||||
// ctx.$rootScope.$digest();
|
||||
// });
|
||||
//
|
||||
// it('should update current value', function() {
|
||||
// expect(variable.tags[0]).to.eq({tag: 'hostname', value: 'server2'});
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
@@ -64,13 +64,13 @@
|
||||
<a href="http://grafana.org" target="_blank">Grafana</a>
|
||||
<span>v[[.BuildVersion]] (commit: [[.BuildCommit]])</span>
|
||||
</li>
|
||||
<li>
|
||||
[[if .NewGrafanaVersionExists]]
|
||||
[[if .NewGrafanaVersionExists]]
|
||||
<li>
|
||||
<a href="http://grafana.org/download" target="_blank" bs-tooltip="'[[.NewGrafanaVersion]]'">
|
||||
New version available!
|
||||
</a>
|
||||
[[end]]
|
||||
</li>
|
||||
</li>
|
||||
[[end]]
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user