mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(plugins): migrating graphite query editor to new model
This commit is contained in:
parent
efdd4a6682
commit
822c8f1575
@ -37,7 +37,7 @@ function pluginDirectiveLoader($compile, datasourceSrv) {
|
||||
name: 'metrics-query-editor-' + ds.meta.id,
|
||||
bindings: {target: "=", panelCtrl: "=", datasource: "="},
|
||||
attrs: {"target": "target", "panel-ctrl": "ctrl", datasource: "datasource"},
|
||||
Component: dsModule.MetricsQueryEditor
|
||||
Component: dsModule.QueryCtrl
|
||||
};
|
||||
});
|
||||
});
|
||||
|
@ -194,6 +194,16 @@ function (angular, $, _, moment) {
|
||||
moment.utc(date).fromNow();
|
||||
};
|
||||
|
||||
p.getNextQueryLetter = function(panel) {
|
||||
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
return _.find(letters, function(refId) {
|
||||
return _.every(panel.targets, function(other) {
|
||||
return other.refId !== refId;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
p._updateSchema = function(old) {
|
||||
var i, j, k;
|
||||
var oldVersion = this.schemaVersion;
|
||||
|
@ -3,6 +3,6 @@ define([
|
||||
'./panel_directive',
|
||||
'./solo_panel_ctrl',
|
||||
'./panel_loader',
|
||||
'./query_editor',
|
||||
'./query_ctrl',
|
||||
'./panel_editor_tab',
|
||||
], function () {});
|
||||
|
@ -5,11 +5,11 @@ import config from 'app/core/config';
|
||||
import {PanelCtrl} from './panel_ctrl';
|
||||
import {MetricsPanelCtrl} from './metrics_panel_ctrl';
|
||||
import {PanelDirective} from './panel_directive';
|
||||
import {QueryEditorCtrl} from './query_editor';
|
||||
import {QueryCtrl} from './query_ctrl';
|
||||
|
||||
export {
|
||||
PanelCtrl,
|
||||
MetricsPanelCtrl,
|
||||
PanelDirective,
|
||||
QueryEditorCtrl,
|
||||
QueryCtrl,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class QueryEditorCtrl {
|
||||
export class QueryCtrl {
|
||||
target: any;
|
||||
datasource: any;
|
||||
panelCtrl: any;
|
||||
@ -27,22 +27,26 @@ export class QueryEditorCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
removeDataQuery(query) {
|
||||
this.panel.targets = _.without(this.panel.targets, query);
|
||||
removeQuery() {
|
||||
this.panel.targets = _.without(this.panel.targets, this.target);
|
||||
this.panelCtrl.refresh();
|
||||
};
|
||||
|
||||
duplicateDataQuery(query) {
|
||||
var clone = angular.copy(query);
|
||||
duplicateQuery() {
|
||||
var clone = angular.copy(this.target);
|
||||
clone.refId = this.getNextQueryLetter();
|
||||
this.panel.targets.push(clone);
|
||||
}
|
||||
|
||||
moveDataQuery(direction) {
|
||||
moveQuery(direction) {
|
||||
var index = _.indexOf(this.panel.targets, this.target);
|
||||
_.move(this.panel.targets, index, index + direction);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
toggleHideQuery() {
|
||||
this.target.hide = !this.target.hide;
|
||||
this.panelCtrl.refresh();
|
@ -1,3 +0,0 @@
|
||||
declare var Datasource: any;
|
||||
export default Datasource;
|
||||
|
@ -1,296 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'app/core/config',
|
||||
'app/core/utils/datemath',
|
||||
'./query_ctrl',
|
||||
'./func_editor',
|
||||
'./add_graphite_func',
|
||||
],
|
||||
function (angular, _, $, config, dateMath) {
|
||||
'use strict';
|
||||
|
||||
/** @ngInject */
|
||||
function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.url = instanceSettings.url;
|
||||
this.name = instanceSettings.name;
|
||||
this.cacheTimeout = instanceSettings.cacheTimeout;
|
||||
this.withCredentials = instanceSettings.withCredentials;
|
||||
this.render_method = instanceSettings.render_method || 'POST';
|
||||
|
||||
this.query = function(options) {
|
||||
try {
|
||||
var graphOptions = {
|
||||
from: this.translateTime(options.rangeRaw.from, false),
|
||||
until: this.translateTime(options.rangeRaw.to, true),
|
||||
targets: options.targets,
|
||||
format: options.format,
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
};
|
||||
|
||||
var params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
||||
if (params.length === 0) {
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
if (options.format === 'png') {
|
||||
return $q.when(this.url + '/render' + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
var httpOptions = { method: this.render_method, url: '/render' };
|
||||
|
||||
if (httpOptions.method === 'GET') {
|
||||
httpOptions.url = httpOptions.url + '?' + params.join('&');
|
||||
}
|
||||
else {
|
||||
httpOptions.data = params.join('&');
|
||||
httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
|
||||
}
|
||||
catch(err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
this.convertDataPointsToMs = function(result) {
|
||||
if (!result || !result.data) { return []; }
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
var series = result.data[i];
|
||||
for (var y = 0; y < series.datapoints.length; y++) {
|
||||
series.datapoints[y][1] *= 1000;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
this.annotationQuery = function(options) {
|
||||
// Graphite metric as annotation
|
||||
if (options.annotation.target) {
|
||||
var target = templateSrv.replace(options.annotation.target);
|
||||
var graphiteQuery = {
|
||||
rangeRaw: options.rangeRaw,
|
||||
targets: [{ target: target }],
|
||||
format: 'json',
|
||||
maxDataPoints: 100
|
||||
};
|
||||
|
||||
return this.query(graphiteQuery)
|
||||
.then(function(result) {
|
||||
var list = [];
|
||||
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
var target = result.data[i];
|
||||
|
||||
for (var y = 0; y < target.datapoints.length; y++) {
|
||||
var datapoint = target.datapoints[y];
|
||||
if (!datapoint[0]) { continue; }
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
time: datapoint[1],
|
||||
title: target.target
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
}
|
||||
// Graphite event as annotation
|
||||
else {
|
||||
var tags = templateSrv.replace(options.annotation.tags);
|
||||
return this.events({range: options.rangeRaw, tags: tags}).then(function(results) {
|
||||
var list = [];
|
||||
for (var i = 0; i < results.data.length; i++) {
|
||||
var e = results.data[i];
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
time: e.when * 1000,
|
||||
title: e.what,
|
||||
tags: e.tags,
|
||||
text: e.data
|
||||
});
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.events = function(options) {
|
||||
try {
|
||||
var tags = '';
|
||||
if (options.tags) {
|
||||
tags = '&tags=' + options.tags;
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest({
|
||||
method: 'GET',
|
||||
url: '/events/get_data?from=' + this.translateTime(options.range.from, false) +
|
||||
'&until=' + this.translateTime(options.range.to, true) + tags,
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
this.translateTime = function(date, roundUp) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
return 'now';
|
||||
}
|
||||
else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
|
||||
date = date.substring(3);
|
||||
date = date.replace('m', 'min');
|
||||
date = date.replace('M', 'mon');
|
||||
return date;
|
||||
}
|
||||
date = dateMath.parse(date, roundUp);
|
||||
}
|
||||
|
||||
// graphite' s from filter is exclusive
|
||||
// here we step back one minute in order
|
||||
// to guarantee that we get all the data that
|
||||
// exists for the specified range
|
||||
if (roundUp) {
|
||||
if (date.get('s')) {
|
||||
date.add(1, 'm');
|
||||
}
|
||||
}
|
||||
else if (roundUp === false) {
|
||||
if (date.get('s')) {
|
||||
date.subtract(1, 'm');
|
||||
}
|
||||
}
|
||||
|
||||
return date.unix();
|
||||
};
|
||||
|
||||
this.metricFindQuery = function(query) {
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = encodeURIComponent(templateSrv.replace(query));
|
||||
}
|
||||
catch(err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest({method: 'GET', url: '/metrics/find/?query=' + interpolated })
|
||||
.then(function(results) {
|
||||
return _.map(results.data, function(metric) {
|
||||
return {
|
||||
text: metric.text,
|
||||
expandable: metric.expandable ? true : false
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function () {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
});
|
||||
};
|
||||
|
||||
this.listDashboards = function(query) {
|
||||
return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} })
|
||||
.then(function(results) {
|
||||
return results.data.dashboards;
|
||||
});
|
||||
};
|
||||
|
||||
this.loadDashboard = function(dashName) {
|
||||
return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) });
|
||||
};
|
||||
|
||||
this.doGraphiteRequest = function(options) {
|
||||
if (this.basicAuth || this.withCredentials) {
|
||||
options.withCredentials = true;
|
||||
}
|
||||
if (this.basicAuth) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers.Authorization = this.basicAuth;
|
||||
}
|
||||
|
||||
options.url = this.url + options.url;
|
||||
options.inspect = { type: 'graphite' };
|
||||
|
||||
return backendSrv.datasourceRequest(options);
|
||||
};
|
||||
|
||||
this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
this.buildGraphiteParams = function(options, scopedVars) {
|
||||
var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
||||
var clean_options = [], targets = {};
|
||||
var target, targetValue, i;
|
||||
var regex = /\#([A-Z])/g;
|
||||
var intervalFormatFixRegex = /'(\d+)m'/gi;
|
||||
var hasTargets = false;
|
||||
|
||||
if (options.format !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
function fixIntervalFormat(match) {
|
||||
return match.replace('m', 'min').replace('M', 'mon');
|
||||
}
|
||||
|
||||
for (i = 0; i < options.targets.length; i++) {
|
||||
target = options.targets[i];
|
||||
if (!target.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!target.refId) {
|
||||
target.refId = this._seriesRefLetters[i];
|
||||
}
|
||||
|
||||
targetValue = templateSrv.replace(target.target, scopedVars);
|
||||
targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
|
||||
targets[target.refId] = targetValue;
|
||||
}
|
||||
|
||||
function nestedSeriesRegexReplacer(match, g1) {
|
||||
return targets[g1];
|
||||
}
|
||||
|
||||
for (i = 0; i < options.targets.length; i++) {
|
||||
target = options.targets[i];
|
||||
if (!target.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targetValue = targets[target.refId];
|
||||
targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
|
||||
targets[target.refId] = targetValue;
|
||||
|
||||
if (!target.hide) {
|
||||
hasTargets = true;
|
||||
clean_options.push("target=" + encodeURIComponent(targetValue));
|
||||
}
|
||||
}
|
||||
|
||||
_.each(options, function (value, key) {
|
||||
if ($.inArray(key, graphite_options) === -1) { return; }
|
||||
if (value) {
|
||||
clean_options.push(key + "=" + encodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasTargets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return clean_options;
|
||||
};
|
||||
}
|
||||
|
||||
return GraphiteDatasource;
|
||||
});
|
281
public/app/plugins/datasource/graphite/datasource.ts
Normal file
281
public/app/plugins/datasource/graphite/datasource.ts
Normal file
@ -0,0 +1,281 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
|
||||
/** @ngInject */
|
||||
export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.url = instanceSettings.url;
|
||||
this.name = instanceSettings.name;
|
||||
this.cacheTimeout = instanceSettings.cacheTimeout;
|
||||
this.withCredentials = instanceSettings.withCredentials;
|
||||
this.render_method = instanceSettings.render_method || 'POST';
|
||||
|
||||
this.query = function(options) {
|
||||
try {
|
||||
var graphOptions = {
|
||||
from: this.translateTime(options.rangeRaw.from, false),
|
||||
until: this.translateTime(options.rangeRaw.to, true),
|
||||
targets: options.targets,
|
||||
format: options.format,
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
};
|
||||
|
||||
var params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
||||
if (params.length === 0) {
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
if (options.format === 'png') {
|
||||
return $q.when(this.url + '/render' + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
var httpOptions: any = {method: this.render_method, url: '/render'};
|
||||
|
||||
if (httpOptions.method === 'GET') {
|
||||
httpOptions.url = httpOptions.url + '?' + params.join('&');
|
||||
} else {
|
||||
httpOptions.data = params.join('&');
|
||||
httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
this.convertDataPointsToMs = function(result) {
|
||||
if (!result || !result.data) { return []; }
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
var series = result.data[i];
|
||||
for (var y = 0; y < series.datapoints.length; y++) {
|
||||
series.datapoints[y][1] *= 1000;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
this.annotationQuery = function(options) {
|
||||
// Graphite metric as annotation
|
||||
if (options.annotation.target) {
|
||||
var target = templateSrv.replace(options.annotation.target);
|
||||
var graphiteQuery = {
|
||||
rangeRaw: options.rangeRaw,
|
||||
targets: [{ target: target }],
|
||||
format: 'json',
|
||||
maxDataPoints: 100
|
||||
};
|
||||
|
||||
return this.query(graphiteQuery)
|
||||
.then(function(result) {
|
||||
var list = [];
|
||||
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
var target = result.data[i];
|
||||
|
||||
for (var y = 0; y < target.datapoints.length; y++) {
|
||||
var datapoint = target.datapoints[y];
|
||||
if (!datapoint[0]) { continue; }
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
time: datapoint[1],
|
||||
title: target.target
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
} else {
|
||||
// Graphite event as annotation
|
||||
var tags = templateSrv.replace(options.annotation.tags);
|
||||
return this.events({range: options.rangeRaw, tags: tags}).then(function(results) {
|
||||
var list = [];
|
||||
for (var i = 0; i < results.data.length; i++) {
|
||||
var e = results.data[i];
|
||||
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
time: e.when * 1000,
|
||||
title: e.what,
|
||||
tags: e.tags,
|
||||
text: e.data
|
||||
});
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.events = function(options) {
|
||||
try {
|
||||
var tags = '';
|
||||
if (options.tags) {
|
||||
tags = '&tags=' + options.tags;
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest({
|
||||
method: 'GET',
|
||||
url: '/events/get_data?from=' + this.translateTime(options.range.from, false) +
|
||||
'&until=' + this.translateTime(options.range.to, true) + tags,
|
||||
});
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
this.translateTime = function(date, roundUp) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
return 'now';
|
||||
} else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
|
||||
date = date.substring(3);
|
||||
date = date.replace('m', 'min');
|
||||
date = date.replace('M', 'mon');
|
||||
return date;
|
||||
}
|
||||
date = dateMath.parse(date, roundUp);
|
||||
}
|
||||
|
||||
// graphite' s from filter is exclusive
|
||||
// here we step back one minute in order
|
||||
// to guarantee that we get all the data that
|
||||
// exists for the specified range
|
||||
if (roundUp) {
|
||||
if (date.get('s')) {
|
||||
date.add(1, 'm');
|
||||
}
|
||||
} else if (roundUp === false) {
|
||||
if (date.get('s')) {
|
||||
date.subtract(1, 'm');
|
||||
}
|
||||
}
|
||||
|
||||
return date.unix();
|
||||
};
|
||||
|
||||
this.metricFindQuery = function(query) {
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = encodeURIComponent(templateSrv.replace(query));
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest({method: 'GET', url: '/metrics/find/?query=' + interpolated })
|
||||
.then(function(results) {
|
||||
return _.map(results.data, function(metric) {
|
||||
return {
|
||||
text: metric.text,
|
||||
expandable: metric.expandable ? true : false
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function () {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
});
|
||||
};
|
||||
|
||||
this.listDashboards = function(query) {
|
||||
return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} })
|
||||
.then(function(results) {
|
||||
return results.data.dashboards;
|
||||
});
|
||||
};
|
||||
|
||||
this.loadDashboard = function(dashName) {
|
||||
return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) });
|
||||
};
|
||||
|
||||
this.doGraphiteRequest = function(options) {
|
||||
if (this.basicAuth || this.withCredentials) {
|
||||
options.withCredentials = true;
|
||||
}
|
||||
if (this.basicAuth) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers.Authorization = this.basicAuth;
|
||||
}
|
||||
|
||||
options.url = this.url + options.url;
|
||||
options.inspect = { type: 'graphite' };
|
||||
|
||||
return backendSrv.datasourceRequest(options);
|
||||
};
|
||||
|
||||
this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
this.buildGraphiteParams = function(options, scopedVars) {
|
||||
var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
||||
var clean_options = [], targets = {};
|
||||
var target, targetValue, i;
|
||||
var regex = /\#([A-Z])/g;
|
||||
var intervalFormatFixRegex = /'(\d+)m'/gi;
|
||||
var hasTargets = false;
|
||||
|
||||
if (options.format !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
function fixIntervalFormat(match) {
|
||||
return match.replace('m', 'min').replace('M', 'mon');
|
||||
}
|
||||
|
||||
for (i = 0; i < options.targets.length; i++) {
|
||||
target = options.targets[i];
|
||||
if (!target.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!target.refId) {
|
||||
target.refId = this._seriesRefLetters[i];
|
||||
}
|
||||
|
||||
targetValue = templateSrv.replace(target.target, scopedVars);
|
||||
targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
|
||||
targets[target.refId] = targetValue;
|
||||
}
|
||||
|
||||
function nestedSeriesRegexReplacer(match, g1) {
|
||||
return targets[g1];
|
||||
}
|
||||
|
||||
for (i = 0; i < options.targets.length; i++) {
|
||||
target = options.targets[i];
|
||||
if (!target.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targetValue = targets[target.refId];
|
||||
targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
|
||||
targets[target.refId] = targetValue;
|
||||
|
||||
if (!target.hide) {
|
||||
hasTargets = true;
|
||||
clean_options.push("target=" + encodeURIComponent(targetValue));
|
||||
}
|
||||
}
|
||||
|
||||
_.each(options, function (value, key) {
|
||||
if (_.indexOf(graphite_options, key) === -1) { return; }
|
||||
if (value) {
|
||||
clean_options.push(key + "=" + encodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasTargets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return clean_options;
|
||||
};
|
||||
}
|
@ -1,682 +0,0 @@
|
||||
define([
|
||||
'lodash'
|
||||
], function(_) {
|
||||
'use strict';
|
||||
|
||||
// This is auto generated from the unicode tables.
|
||||
// The tables are at:
|
||||
// http://www.fileformat.info/info/unicode/category/Lu/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Ll/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lt/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lm/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lo/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Nl/list.htm
|
||||
|
||||
var unicodeLetterTable = [
|
||||
170, 170, 181, 181, 186, 186, 192, 214,
|
||||
216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750,
|
||||
880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908,
|
||||
910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366,
|
||||
1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610,
|
||||
1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775,
|
||||
1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957,
|
||||
1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069,
|
||||
2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361,
|
||||
2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431,
|
||||
2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482,
|
||||
2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529,
|
||||
2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608,
|
||||
2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654,
|
||||
2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736,
|
||||
2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785,
|
||||
2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867,
|
||||
2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929,
|
||||
2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970,
|
||||
2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001,
|
||||
3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123,
|
||||
3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212,
|
||||
3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261,
|
||||
3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344,
|
||||
3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455,
|
||||
3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526,
|
||||
3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716,
|
||||
3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743,
|
||||
3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760,
|
||||
3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805,
|
||||
3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138,
|
||||
4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198,
|
||||
4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346,
|
||||
4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696,
|
||||
4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789,
|
||||
4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880,
|
||||
4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740,
|
||||
5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900,
|
||||
5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000,
|
||||
6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312,
|
||||
6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516,
|
||||
6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823,
|
||||
6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141,
|
||||
7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409,
|
||||
7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013,
|
||||
8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061,
|
||||
8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140,
|
||||
8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188,
|
||||
8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455,
|
||||
8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486,
|
||||
8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521,
|
||||
8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358,
|
||||
11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621,
|
||||
11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694,
|
||||
11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726,
|
||||
11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295,
|
||||
12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438,
|
||||
12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589,
|
||||
12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312,
|
||||
19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124,
|
||||
42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539,
|
||||
42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783,
|
||||
42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921,
|
||||
43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042,
|
||||
43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259,
|
||||
43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442,
|
||||
43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595,
|
||||
43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697,
|
||||
43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714,
|
||||
43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798,
|
||||
43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032,
|
||||
55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045,
|
||||
64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279,
|
||||
64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316,
|
||||
64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433,
|
||||
64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019,
|
||||
65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370,
|
||||
65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495,
|
||||
65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594,
|
||||
65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786,
|
||||
65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334,
|
||||
66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511,
|
||||
66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592,
|
||||
67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669,
|
||||
67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115,
|
||||
68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405,
|
||||
68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687,
|
||||
69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894,
|
||||
92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964,
|
||||
119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980,
|
||||
119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069,
|
||||
120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121,
|
||||
120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144,
|
||||
120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570,
|
||||
120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686,
|
||||
120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779,
|
||||
131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972,
|
||||
177984, 177984, 178205, 178205, 194560, 195101
|
||||
];
|
||||
|
||||
var identifierStartTable = [];
|
||||
|
||||
for (var i = 0; i < 128; i++) {
|
||||
identifierStartTable[i] =
|
||||
i >= 48 && i <= 57 || // 0-9
|
||||
i === 36 || // $
|
||||
i === 126 || // ~
|
||||
i === 124 || // |
|
||||
i >= 65 && i <= 90 || // A-Z
|
||||
i === 95 || // _
|
||||
i === 45 || // -
|
||||
i === 42 || // *
|
||||
i === 58 || // :
|
||||
i === 91 || // templateStart [
|
||||
i === 93 || // templateEnd ]
|
||||
i === 63 || // ?
|
||||
i === 37 || // %
|
||||
i === 35 || // #
|
||||
i === 61 || // =
|
||||
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
|
||||
}
|
||||
|
||||
function Lexer(expression) {
|
||||
this.input = expression;
|
||||
this.char = 1;
|
||||
this.from = 1;
|
||||
}
|
||||
|
||||
Lexer.prototype = {
|
||||
|
||||
peek: function (i) {
|
||||
return this.input.charAt(i || 0);
|
||||
},
|
||||
|
||||
skip: function (i) {
|
||||
i = i || 1;
|
||||
this.char += i;
|
||||
this.input = this.input.slice(i);
|
||||
},
|
||||
|
||||
tokenize: function() {
|
||||
var list = [];
|
||||
var token;
|
||||
while (token = this.next()) {
|
||||
list.push(token);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
next: function() {
|
||||
this.from = this.char;
|
||||
|
||||
// Move to the next non-space character.
|
||||
var start;
|
||||
if (/\s/.test(this.peek())) {
|
||||
start = this.char;
|
||||
|
||||
while (/\s/.test(this.peek())) {
|
||||
this.from += 1;
|
||||
this.skip();
|
||||
}
|
||||
|
||||
if (this.peek() === "") { // EOL
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var match = this.scanStringLiteral();
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
|
||||
match =
|
||||
this.scanPunctuator() ||
|
||||
this.scanNumericLiteral() ||
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence();
|
||||
|
||||
if (match) {
|
||||
this.skip(match.value.length);
|
||||
return match;
|
||||
}
|
||||
|
||||
// No token could be matched, give up.
|
||||
return null;
|
||||
},
|
||||
|
||||
scanTemplateSequence: function() {
|
||||
if (this.peek() === '[' && this.peek(1) === '[') {
|
||||
return {
|
||||
type: 'templateStart',
|
||||
value: '[[',
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
if (this.peek() === ']' && this.peek(1) === ']') {
|
||||
return {
|
||||
type: 'templateEnd',
|
||||
value: '[[',
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a JavaScript identifier out of the next sequence of
|
||||
* characters or return 'null' if its not possible. In addition,
|
||||
* to Identifier this method can also produce BooleanLiteral
|
||||
* (true/false) and NullLiteral (null).
|
||||
*/
|
||||
scanIdentifier: function() {
|
||||
var id = "";
|
||||
var index = 0;
|
||||
var type, char;
|
||||
|
||||
// Detects any character in the Unicode categories "Uppercase
|
||||
// letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter
|
||||
// (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or
|
||||
// "Letter number (Nl)".
|
||||
//
|
||||
// Both approach and unicodeLetterTable were borrowed from
|
||||
// Google's Traceur.
|
||||
|
||||
function isUnicodeLetter(code) {
|
||||
for (var i = 0; i < unicodeLetterTable.length;) {
|
||||
if (code < unicodeLetterTable[i++]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code <= unicodeLetterTable[i++]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isHexDigit(str) {
|
||||
return (/^[0-9a-fA-F]$/).test(str);
|
||||
}
|
||||
|
||||
var readUnicodeEscapeSequence = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
index += 1;
|
||||
|
||||
if (this.peek(index) !== "u") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ch1 = this.peek(index + 1);
|
||||
var ch2 = this.peek(index + 2);
|
||||
var ch3 = this.peek(index + 3);
|
||||
var ch4 = this.peek(index + 4);
|
||||
var code;
|
||||
|
||||
if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
|
||||
code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 5;
|
||||
return "\\u" + ch1 + ch2 + ch3 + ch4;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
var getIdentifierStart = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
var chr = this.peek(index);
|
||||
var code = chr.charCodeAt(0);
|
||||
|
||||
if (chr === '*') {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
if (code === 92) {
|
||||
return readUnicodeEscapeSequence();
|
||||
}
|
||||
|
||||
if (code < 128) {
|
||||
if (identifierStartTable[code]) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
var getIdentifierPart = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
var chr = this.peek(index);
|
||||
var code = chr.charCodeAt(0);
|
||||
|
||||
if (code === 92) {
|
||||
return readUnicodeEscapeSequence();
|
||||
}
|
||||
|
||||
if (code < 128) {
|
||||
if (identifierPartTable[code]) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
char = getIdentifierStart();
|
||||
if (char === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
id = char;
|
||||
for (;;) {
|
||||
char = getIdentifierPart();
|
||||
|
||||
if (char === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
id += char;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case 'true': {
|
||||
type = 'bool';
|
||||
break;
|
||||
}
|
||||
case 'false': {
|
||||
type = 'bool';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
type = "identifier";
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
value: id,
|
||||
pos: this.char
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a numeric literal out of the next sequence of
|
||||
* characters or return 'null' if its not possible. This method
|
||||
* supports all numeric literals described in section 7.8.3
|
||||
* of the EcmaScript 5 specification.
|
||||
*
|
||||
* This method's implementation was heavily influenced by the
|
||||
* scanNumericLiteral function in the Esprima parser's source code.
|
||||
*/
|
||||
scanNumericLiteral: function () {
|
||||
var index = 0;
|
||||
var value = "";
|
||||
var length = this.input.length;
|
||||
var char = this.peek(index);
|
||||
var bad;
|
||||
|
||||
function isDecimalDigit(str) {
|
||||
return (/^[0-9]$/).test(str);
|
||||
}
|
||||
|
||||
function isOctalDigit(str) {
|
||||
return (/^[0-7]$/).test(str);
|
||||
}
|
||||
|
||||
function isHexDigit(str) {
|
||||
return (/^[0-9a-fA-F]$/).test(str);
|
||||
}
|
||||
|
||||
function isIdentifierStart(ch) {
|
||||
return (ch === "$") || (ch === "_") || (ch === "\\") ||
|
||||
(ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
|
||||
}
|
||||
|
||||
// handle negative num literals
|
||||
if (char === '-') {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
}
|
||||
|
||||
// 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 (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;
|
||||
}
|
||||
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: 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
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return Lexer;
|
||||
|
||||
});
|
678
public/app/plugins/datasource/graphite/lexer.ts
Normal file
678
public/app/plugins/datasource/graphite/lexer.ts
Normal file
@ -0,0 +1,678 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
// This is auto generated from the unicode tables.
|
||||
// The tables are at:
|
||||
// http://www.fileformat.info/info/unicode/category/Lu/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Ll/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lt/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lm/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Lo/list.htm
|
||||
// http://www.fileformat.info/info/unicode/category/Nl/list.htm
|
||||
|
||||
var unicodeLetterTable = [
|
||||
170, 170, 181, 181, 186, 186, 192, 214,
|
||||
216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750,
|
||||
880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908,
|
||||
910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366,
|
||||
1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610,
|
||||
1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775,
|
||||
1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957,
|
||||
1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069,
|
||||
2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361,
|
||||
2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431,
|
||||
2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482,
|
||||
2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529,
|
||||
2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608,
|
||||
2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654,
|
||||
2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736,
|
||||
2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785,
|
||||
2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867,
|
||||
2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929,
|
||||
2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970,
|
||||
2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001,
|
||||
3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123,
|
||||
3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212,
|
||||
3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261,
|
||||
3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344,
|
||||
3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455,
|
||||
3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526,
|
||||
3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716,
|
||||
3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743,
|
||||
3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760,
|
||||
3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805,
|
||||
3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138,
|
||||
4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198,
|
||||
4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346,
|
||||
4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696,
|
||||
4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789,
|
||||
4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880,
|
||||
4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740,
|
||||
5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900,
|
||||
5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000,
|
||||
6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312,
|
||||
6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516,
|
||||
6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823,
|
||||
6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141,
|
||||
7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409,
|
||||
7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013,
|
||||
8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061,
|
||||
8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140,
|
||||
8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188,
|
||||
8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455,
|
||||
8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486,
|
||||
8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521,
|
||||
8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358,
|
||||
11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621,
|
||||
11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694,
|
||||
11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726,
|
||||
11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295,
|
||||
12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438,
|
||||
12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589,
|
||||
12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312,
|
||||
19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124,
|
||||
42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539,
|
||||
42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783,
|
||||
42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921,
|
||||
43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042,
|
||||
43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259,
|
||||
43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442,
|
||||
43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595,
|
||||
43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697,
|
||||
43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714,
|
||||
43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798,
|
||||
43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032,
|
||||
55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045,
|
||||
64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279,
|
||||
64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316,
|
||||
64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433,
|
||||
64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019,
|
||||
65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370,
|
||||
65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495,
|
||||
65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594,
|
||||
65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786,
|
||||
65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334,
|
||||
66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511,
|
||||
66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592,
|
||||
67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669,
|
||||
67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115,
|
||||
68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405,
|
||||
68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687,
|
||||
69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894,
|
||||
92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964,
|
||||
119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980,
|
||||
119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069,
|
||||
120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121,
|
||||
120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144,
|
||||
120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570,
|
||||
120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686,
|
||||
120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779,
|
||||
131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972,
|
||||
177984, 177984, 178205, 178205, 194560, 195101
|
||||
];
|
||||
|
||||
var identifierStartTable = [];
|
||||
|
||||
for (var i = 0; i < 128; i++) {
|
||||
identifierStartTable[i] =
|
||||
i >= 48 && i <= 57 || // 0-9
|
||||
i === 36 || // $
|
||||
i === 126 || // ~
|
||||
i === 124 || // |
|
||||
i >= 65 && i <= 90 || // A-Z
|
||||
i === 95 || // _
|
||||
i === 45 || // -
|
||||
i === 42 || // *
|
||||
i === 58 || // :
|
||||
i === 91 || // templateStart [
|
||||
i === 93 || // templateEnd ]
|
||||
i === 63 || // ?
|
||||
i === 37 || // %
|
||||
i === 35 || // #
|
||||
i === 61 || // =
|
||||
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
|
||||
}
|
||||
|
||||
export function Lexer(expression) {
|
||||
this.input = expression;
|
||||
this.char = 1;
|
||||
this.from = 1;
|
||||
}
|
||||
|
||||
Lexer.prototype = {
|
||||
|
||||
peek: function (i) {
|
||||
return this.input.charAt(i || 0);
|
||||
},
|
||||
|
||||
skip: function (i) {
|
||||
i = i || 1;
|
||||
this.char += i;
|
||||
this.input = this.input.slice(i);
|
||||
},
|
||||
|
||||
tokenize: function() {
|
||||
var list = [];
|
||||
var token;
|
||||
while (token = this.next()) {
|
||||
list.push(token);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
next: function() {
|
||||
this.from = this.char;
|
||||
|
||||
// Move to the next non-space character.
|
||||
var start;
|
||||
if (/\s/.test(this.peek())) {
|
||||
start = this.char;
|
||||
|
||||
while (/\s/.test(this.peek())) {
|
||||
this.from += 1;
|
||||
this.skip();
|
||||
}
|
||||
|
||||
if (this.peek() === "") { // EOL
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var match = this.scanStringLiteral();
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
|
||||
match =
|
||||
this.scanPunctuator() ||
|
||||
this.scanNumericLiteral() ||
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence();
|
||||
|
||||
if (match) {
|
||||
this.skip(match.value.length);
|
||||
return match;
|
||||
}
|
||||
|
||||
// No token could be matched, give up.
|
||||
return null;
|
||||
},
|
||||
|
||||
scanTemplateSequence: function() {
|
||||
if (this.peek() === '[' && this.peek(1) === '[') {
|
||||
return {
|
||||
type: 'templateStart',
|
||||
value: '[[',
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
if (this.peek() === ']' && this.peek(1) === ']') {
|
||||
return {
|
||||
type: 'templateEnd',
|
||||
value: '[[',
|
||||
pos: this.char
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a JavaScript identifier out of the next sequence of
|
||||
* characters or return 'null' if its not possible. In addition,
|
||||
* to Identifier this method can also produce BooleanLiteral
|
||||
* (true/false) and NullLiteral (null).
|
||||
*/
|
||||
scanIdentifier: function() {
|
||||
var id = "";
|
||||
var index = 0;
|
||||
var type, char;
|
||||
|
||||
// Detects any character in the Unicode categories "Uppercase
|
||||
// letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter
|
||||
// (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or
|
||||
// "Letter number (Nl)".
|
||||
//
|
||||
// Both approach and unicodeLetterTable were borrowed from
|
||||
// Google's Traceur.
|
||||
|
||||
function isUnicodeLetter(code) {
|
||||
for (var i = 0; i < unicodeLetterTable.length;) {
|
||||
if (code < unicodeLetterTable[i++]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code <= unicodeLetterTable[i++]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isHexDigit(str) {
|
||||
return (/^[0-9a-fA-F]$/).test(str);
|
||||
}
|
||||
|
||||
var readUnicodeEscapeSequence = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
index += 1;
|
||||
|
||||
if (this.peek(index) !== "u") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ch1 = this.peek(index + 1);
|
||||
var ch2 = this.peek(index + 2);
|
||||
var ch3 = this.peek(index + 3);
|
||||
var ch4 = this.peek(index + 4);
|
||||
var code;
|
||||
|
||||
if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
|
||||
code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 5;
|
||||
return "\\u" + ch1 + ch2 + ch3 + ch4;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
var getIdentifierStart = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
var chr = this.peek(index);
|
||||
var code = chr.charCodeAt(0);
|
||||
|
||||
if (chr === '*') {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
if (code === 92) {
|
||||
return readUnicodeEscapeSequence();
|
||||
}
|
||||
|
||||
if (code < 128) {
|
||||
if (identifierStartTable[code]) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
var getIdentifierPart = _.bind(function () {
|
||||
/*jshint validthis:true */
|
||||
var chr = this.peek(index);
|
||||
var code = chr.charCodeAt(0);
|
||||
|
||||
if (code === 92) {
|
||||
return readUnicodeEscapeSequence();
|
||||
}
|
||||
|
||||
if (code < 128) {
|
||||
if (identifierPartTable[code]) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isUnicodeLetter(code)) {
|
||||
index += 1;
|
||||
return chr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, this);
|
||||
|
||||
char = getIdentifierStart();
|
||||
if (char === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
id = char;
|
||||
for (;;) {
|
||||
char = getIdentifierPart();
|
||||
|
||||
if (char === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
id += char;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case 'true': {
|
||||
type = 'bool';
|
||||
break;
|
||||
}
|
||||
case 'false': {
|
||||
type = 'bool';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
type = "identifier";
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
value: id,
|
||||
pos: this.char
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
* Extract a numeric literal out of the next sequence of
|
||||
* characters or return 'null' if its not possible. This method
|
||||
* supports all numeric literals described in section 7.8.3
|
||||
* of the EcmaScript 5 specification.
|
||||
*
|
||||
* This method's implementation was heavily influenced by the
|
||||
* scanNumericLiteral function in the Esprima parser's source code.
|
||||
*/
|
||||
scanNumericLiteral: function (): any {
|
||||
var index = 0;
|
||||
var value = "";
|
||||
var length = this.input.length;
|
||||
var char = this.peek(index);
|
||||
var bad;
|
||||
|
||||
function isDecimalDigit(str) {
|
||||
return (/^[0-9]$/).test(str);
|
||||
}
|
||||
|
||||
function isOctalDigit(str) {
|
||||
return (/^[0-7]$/).test(str);
|
||||
}
|
||||
|
||||
function isHexDigit(str) {
|
||||
return (/^[0-9a-fA-F]$/).test(str);
|
||||
}
|
||||
|
||||
function isIdentifierStart(ch) {
|
||||
return (ch === "$") || (ch === "_") || (ch === "\\") ||
|
||||
(ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
|
||||
}
|
||||
|
||||
// handle negative num literals
|
||||
if (char === '-') {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
}
|
||||
|
||||
// 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 (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;
|
||||
}
|
||||
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: 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
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -1,38 +0,0 @@
|
||||
define([
|
||||
'./datasource',
|
||||
],
|
||||
function (GraphiteDatasource) {
|
||||
'use strict';
|
||||
|
||||
function metricsQueryEditor() {
|
||||
return {
|
||||
controller: 'GraphiteQueryCtrl',
|
||||
templateUrl: 'public/app/plugins/datasource/graphite/partials/query.editor.html'
|
||||
};
|
||||
}
|
||||
|
||||
function metricsQueryOptions() {
|
||||
return {templateUrl: 'public/app/plugins/datasource/graphite/partials/query.options.html'};
|
||||
}
|
||||
|
||||
function annotationsQueryEditor() {
|
||||
return {templateUrl: 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'};
|
||||
}
|
||||
|
||||
function configView() {
|
||||
return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'};
|
||||
}
|
||||
|
||||
function ConfigView() {
|
||||
}
|
||||
ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html';
|
||||
|
||||
return {
|
||||
Datasource: GraphiteDatasource,
|
||||
configView: configView,
|
||||
annotationsQueryEditor: annotationsQueryEditor,
|
||||
metricsQueryEditor: metricsQueryEditor,
|
||||
metricsQueryOptions: metricsQueryOptions,
|
||||
ConfigView: ConfigView
|
||||
};
|
||||
});
|
51
public/app/plugins/datasource/graphite/module.ts
Normal file
51
public/app/plugins/datasource/graphite/module.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {GraphiteDatasource} from './datasource';
|
||||
import {GraphiteQueryCtrl} from './query_ctrl';
|
||||
|
||||
class GraphiteConfigView {
|
||||
static templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html';
|
||||
}
|
||||
|
||||
export {
|
||||
GraphiteDatasource as Datasource,
|
||||
GraphiteQueryCtrl as QueryCtrl,
|
||||
GraphiteConfigView as ConfigView
|
||||
};
|
||||
|
||||
// define([
|
||||
// './datasource',
|
||||
// ],
|
||||
// function (GraphiteDatasource) {
|
||||
// 'use strict';
|
||||
//
|
||||
// function metricsQueryEditor() {
|
||||
// return {
|
||||
// controller: 'GraphiteQueryCtrl',
|
||||
// templateUrl: 'public/app/plugins/datasource/graphite/partials/query.editor.html'
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// function metricsQueryOptions() {
|
||||
// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/query.options.html'};
|
||||
// }
|
||||
//
|
||||
// function annotationsQueryEditor() {
|
||||
// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/annotations.editor.html'};
|
||||
// }
|
||||
//
|
||||
// function configView() {
|
||||
// return {templateUrl: 'public/app/plugins/datasource/graphite/partials/config.html'};
|
||||
// }
|
||||
//
|
||||
// function ConfigView() {
|
||||
// }
|
||||
// ConfigView.templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html';
|
||||
//
|
||||
// return {
|
||||
// Datasource: GraphiteDatasource,
|
||||
// configView: configView,
|
||||
// annotationsQueryEditor: annotationsQueryEditor,
|
||||
// metricsQueryEditor: metricsQueryEditor,
|
||||
// metricsQueryOptions: metricsQueryOptions,
|
||||
// ConfigView: ConfigView
|
||||
// };
|
||||
// });
|
@ -1,265 +0,0 @@
|
||||
define([
|
||||
'./lexer'
|
||||
], function (Lexer) {
|
||||
'use strict';
|
||||
|
||||
function Parser(expression) {
|
||||
this.expression = expression;
|
||||
this.lexer = new Lexer(expression);
|
||||
this.tokens = this.lexer.tokenize();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
Parser.prototype = {
|
||||
|
||||
getAst: function () {
|
||||
return this.start();
|
||||
},
|
||||
|
||||
start: function () {
|
||||
try {
|
||||
return this.functionCall() || this.metricExpression();
|
||||
}
|
||||
catch (e) {
|
||||
return {
|
||||
type: 'error',
|
||||
message: e.message,
|
||||
pos: e.pos
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
curlyBraceSegment: function() {
|
||||
if (this.match('identifier', '{') || this.match('{')) {
|
||||
|
||||
var curlySegment = "";
|
||||
|
||||
while (!this.match('') && !this.match('}')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
if (!this.match('}')) {
|
||||
this.errorMark("Expected closing '}'");
|
||||
}
|
||||
|
||||
curlySegment += this.consumeToken().value;
|
||||
|
||||
// if curly segment is directly followed by identifier
|
||||
// include it in the segment
|
||||
if (this.match('identifier')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: curlySegment
|
||||
};
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
metricSegment: function() {
|
||||
var curly = this.curlyBraceSegment();
|
||||
if (curly) {
|
||||
return curly;
|
||||
}
|
||||
|
||||
if (this.match('identifier') || this.match('number')) {
|
||||
// hack to handle float numbers in metric segments
|
||||
var parts = this.consumeToken().value.split('.');
|
||||
if (parts.length === 2) {
|
||||
this.tokens.splice(this.index, 0, { type: '.' });
|
||||
this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] });
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: parts[0]
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.match('templateStart')) {
|
||||
this.errorMark('Expected metric identifier');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
|
||||
if (!this.match('identifier')) {
|
||||
this.errorMark('Expected identifier after templateStart');
|
||||
}
|
||||
|
||||
var node = {
|
||||
type: 'template',
|
||||
value: this.consumeToken().value
|
||||
};
|
||||
|
||||
if (!this.match('templateEnd')) {
|
||||
this.errorMark('Expected templateEnd');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
return node;
|
||||
},
|
||||
|
||||
metricExpression: function() {
|
||||
if (!this.match('templateStart') &&
|
||||
!this.match('identifier') &&
|
||||
!this.match('number') &&
|
||||
!this.match('{')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var node = {
|
||||
type: 'metric',
|
||||
segments: []
|
||||
};
|
||||
|
||||
node.segments.push(this.metricSegment());
|
||||
|
||||
while (this.match('.')) {
|
||||
this.consumeToken();
|
||||
|
||||
var segment = this.metricSegment();
|
||||
if (!segment) {
|
||||
this.errorMark('Expected metric identifier');
|
||||
}
|
||||
|
||||
node.segments.push(segment);
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
functionCall: function() {
|
||||
if (!this.match('identifier', '(')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var node = {
|
||||
type: 'function',
|
||||
name: this.consumeToken().value,
|
||||
};
|
||||
|
||||
// consume left parenthesis
|
||||
this.consumeToken();
|
||||
|
||||
node.params = this.functionParameters();
|
||||
|
||||
if (!this.match(')')) {
|
||||
this.errorMark('Expected closing parenthesis');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
boolExpression: function() {
|
||||
if (!this.match('bool')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'bool',
|
||||
value: this.consumeToken().value === 'true',
|
||||
};
|
||||
},
|
||||
|
||||
functionParameters: function () {
|
||||
if (this.match(')') || this.match('')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var param =
|
||||
this.functionCall() ||
|
||||
this.numericLiteral() ||
|
||||
this.seriesRefExpression() ||
|
||||
this.boolExpression() ||
|
||||
this.metricExpression() ||
|
||||
this.stringLiteral();
|
||||
|
||||
if (!this.match(',')) {
|
||||
return [param];
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
return [param].concat(this.functionParameters());
|
||||
},
|
||||
|
||||
seriesRefExpression: function() {
|
||||
if (!this.match('identifier')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = this.tokens[this.index].value;
|
||||
if (!value.match(/\#[A-Z]/)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = this.consumeToken();
|
||||
|
||||
return {
|
||||
type: 'series-ref',
|
||||
value: token.value
|
||||
};
|
||||
},
|
||||
|
||||
numericLiteral: function () {
|
||||
if (!this.match('number')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: parseFloat(this.consumeToken().value)
|
||||
};
|
||||
},
|
||||
|
||||
stringLiteral: function () {
|
||||
if (!this.match('string')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = this.consumeToken();
|
||||
if (token.isUnclosed) {
|
||||
throw { message: 'Unclosed string parameter', pos: token.pos };
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'string',
|
||||
value: token.value
|
||||
};
|
||||
},
|
||||
|
||||
errorMark: function(text) {
|
||||
var currentToken = this.tokens[this.index];
|
||||
var type = currentToken ? currentToken.type : 'end of string';
|
||||
throw {
|
||||
message: text + " instead found " + type,
|
||||
pos: currentToken ? currentToken.pos : this.lexer.char
|
||||
};
|
||||
},
|
||||
|
||||
// returns token value and incre
|
||||
consumeToken: function() {
|
||||
this.index++;
|
||||
return this.tokens[this.index - 1];
|
||||
},
|
||||
|
||||
matchToken: function(type, index) {
|
||||
var token = this.tokens[this.index + index];
|
||||
return (token === undefined && type === '') ||
|
||||
token && token.type === type;
|
||||
},
|
||||
|
||||
match: function(token1, token2) {
|
||||
return this.matchToken(token1, 0) &&
|
||||
(!token2 || this.matchToken(token2, 1));
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return Parser;
|
||||
});
|
258
public/app/plugins/datasource/graphite/parser.ts
Normal file
258
public/app/plugins/datasource/graphite/parser.ts
Normal file
@ -0,0 +1,258 @@
|
||||
|
||||
import {Lexer} from './lexer';
|
||||
|
||||
export function Parser(expression) {
|
||||
this.expression = expression;
|
||||
this.lexer = new Lexer(expression);
|
||||
this.tokens = this.lexer.tokenize();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
Parser.prototype = {
|
||||
|
||||
getAst: function () {
|
||||
return this.start();
|
||||
},
|
||||
|
||||
start: function () {
|
||||
try {
|
||||
return this.functionCall() || this.metricExpression();
|
||||
} catch (e) {
|
||||
return {
|
||||
type: 'error',
|
||||
message: e.message,
|
||||
pos: e.pos
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
curlyBraceSegment: function() {
|
||||
if (this.match('identifier', '{') || this.match('{')) {
|
||||
|
||||
var curlySegment = "";
|
||||
|
||||
while (!this.match('') && !this.match('}')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
if (!this.match('}')) {
|
||||
this.errorMark("Expected closing '}'");
|
||||
}
|
||||
|
||||
curlySegment += this.consumeToken().value;
|
||||
|
||||
// if curly segment is directly followed by identifier
|
||||
// include it in the segment
|
||||
if (this.match('identifier')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: curlySegment
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
metricSegment: function() {
|
||||
var curly = this.curlyBraceSegment();
|
||||
if (curly) {
|
||||
return curly;
|
||||
}
|
||||
|
||||
if (this.match('identifier') || this.match('number')) {
|
||||
// hack to handle float numbers in metric segments
|
||||
var parts = this.consumeToken().value.split('.');
|
||||
if (parts.length === 2) {
|
||||
this.tokens.splice(this.index, 0, { type: '.' });
|
||||
this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] });
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: parts[0]
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.match('templateStart')) {
|
||||
this.errorMark('Expected metric identifier');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
|
||||
if (!this.match('identifier')) {
|
||||
this.errorMark('Expected identifier after templateStart');
|
||||
}
|
||||
|
||||
var node = {
|
||||
type: 'template',
|
||||
value: this.consumeToken().value
|
||||
};
|
||||
|
||||
if (!this.match('templateEnd')) {
|
||||
this.errorMark('Expected templateEnd');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
return node;
|
||||
},
|
||||
|
||||
metricExpression: function() {
|
||||
if (!this.match('templateStart') &&
|
||||
!this.match('identifier') &&
|
||||
!this.match('number') &&
|
||||
!this.match('{')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var node = {
|
||||
type: 'metric',
|
||||
segments: []
|
||||
};
|
||||
|
||||
node.segments.push(this.metricSegment());
|
||||
|
||||
while (this.match('.')) {
|
||||
this.consumeToken();
|
||||
|
||||
var segment = this.metricSegment();
|
||||
if (!segment) {
|
||||
this.errorMark('Expected metric identifier');
|
||||
}
|
||||
|
||||
node.segments.push(segment);
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
functionCall: function() {
|
||||
if (!this.match('identifier', '(')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var node: any = {
|
||||
type: 'function',
|
||||
name: this.consumeToken().value,
|
||||
};
|
||||
|
||||
// consume left parenthesis
|
||||
this.consumeToken();
|
||||
|
||||
node.params = this.functionParameters();
|
||||
|
||||
if (!this.match(')')) {
|
||||
this.errorMark('Expected closing parenthesis');
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
boolExpression: function() {
|
||||
if (!this.match('bool')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'bool',
|
||||
value: this.consumeToken().value === 'true',
|
||||
};
|
||||
},
|
||||
|
||||
functionParameters: function () {
|
||||
if (this.match(')') || this.match('')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var param =
|
||||
this.functionCall() ||
|
||||
this.numericLiteral() ||
|
||||
this.seriesRefExpression() ||
|
||||
this.boolExpression() ||
|
||||
this.metricExpression() ||
|
||||
this.stringLiteral();
|
||||
|
||||
if (!this.match(',')) {
|
||||
return [param];
|
||||
}
|
||||
|
||||
this.consumeToken();
|
||||
return [param].concat(this.functionParameters());
|
||||
},
|
||||
|
||||
seriesRefExpression: function() {
|
||||
if (!this.match('identifier')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = this.tokens[this.index].value;
|
||||
if (!value.match(/\#[A-Z]/)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = this.consumeToken();
|
||||
|
||||
return {
|
||||
type: 'series-ref',
|
||||
value: token.value
|
||||
};
|
||||
},
|
||||
|
||||
numericLiteral: function () {
|
||||
if (!this.match('number')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: parseFloat(this.consumeToken().value)
|
||||
};
|
||||
},
|
||||
|
||||
stringLiteral: function () {
|
||||
if (!this.match('string')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = this.consumeToken();
|
||||
if (token.isUnclosed) {
|
||||
throw { message: 'Unclosed string parameter', pos: token.pos };
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'string',
|
||||
value: token.value
|
||||
};
|
||||
},
|
||||
|
||||
errorMark: function(text) {
|
||||
var currentToken = this.tokens[this.index];
|
||||
var type = currentToken ? currentToken.type : 'end of string';
|
||||
throw {
|
||||
message: text + " instead found " + type,
|
||||
pos: currentToken ? currentToken.pos : this.lexer.char
|
||||
};
|
||||
},
|
||||
|
||||
// returns token value and incre
|
||||
consumeToken: function() {
|
||||
this.index++;
|
||||
return this.tokens[this.index - 1];
|
||||
},
|
||||
|
||||
matchToken: function(type, index) {
|
||||
var token = this.tokens[this.index + index];
|
||||
return (token === undefined && type === '') ||
|
||||
token && token.type === type;
|
||||
},
|
||||
|
||||
match: function(token1, token2) {
|
||||
return this.matchToken(token1, 0) &&
|
||||
(!token2 || this.matchToken(token2, 1));
|
||||
},
|
||||
};
|
||||
|
@ -1,15 +1,15 @@
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li ng-show="parserError" class="tight-form-item">
|
||||
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
<li ng-show="ctrl.parserError" class="tight-form-item">
|
||||
<a bs-tooltip="ctrl.parserError" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tight-form-item small" ng-show="target.datasource">
|
||||
<em>{{target.datasource}}</em>
|
||||
<li class="tight-form-item small" ng-show="ctrl.target.datasource">
|
||||
<em>{{ctrl.target.datasource}}</em>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<a class="pointer" tabindex="1" ng-click="toggleEditorMode()">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.toggleEditorMode()">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</li>
|
||||
@ -20,24 +20,24 @@
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="toggleEditorMode()">
|
||||
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">
|
||||
Switch editor mode
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.duplicateDataQuery(target)">Duplicate</a>
|
||||
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index-1)">Move up</a>
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index+1)">Move down</a>
|
||||
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeDataQuery(target)">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(target)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
@ -45,24 +45,24 @@
|
||||
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">
|
||||
{{target.refId}}
|
||||
{{ctrl.target.refId}}
|
||||
</li>
|
||||
<li>
|
||||
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
|
||||
<a class="tight-form-item" ng-click="ctrl.toggleHideQuery()" role="menuitem">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span style="display: block; overflow: hidden;">
|
||||
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="panelCtrl.getData()" ng-show="target.textEditor"></input>
|
||||
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="ctrl.target.target" give-focus="ctrl.target.textEditor" spellcheck='false' ng-model-onblur ng-change="ctrl.refresh()" ng-show="ctrl.target.textEditor"></input>
|
||||
</span>
|
||||
|
||||
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
|
||||
<li ng-repeat="segment in segments" role="menuitem">
|
||||
<metric-segment segment="segment" get-options="getAltSegments($index)" on-change="segmentValueChanged(segment, $index)"></metric-segment>
|
||||
<ul class="tight-form-list" role="menu" ng-hide="ctrl.target.textEditor">
|
||||
<li ng-repeat="segment in ctrl.segments" role="menuitem">
|
||||
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
<li ng-repeat="func in functions">
|
||||
<li ng-repeat="func in ctrl.functions">
|
||||
<span graphite-func-editor class="tight-form-item tight-form-func">
|
||||
</span>
|
||||
</li>
|
||||
|
@ -1,292 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'app/core/config',
|
||||
'./gfunc',
|
||||
'./parser'
|
||||
],
|
||||
function (angular, _, config, gfunc, Parser) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GraphiteQueryCtrl', function($scope, uiSegmentSrv, templateSrv) {
|
||||
var panelCtrl = $scope.panelCtrl = $scope.ctrl;
|
||||
var datasource = $scope.datasource;
|
||||
|
||||
$scope.init = function() {
|
||||
if ($scope.target) {
|
||||
$scope.target.target = $scope.target.target || '';
|
||||
parseTarget();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleEditorMode = function() {
|
||||
$scope.target.textEditor = !$scope.target.textEditor;
|
||||
parseTarget();
|
||||
};
|
||||
|
||||
// The way parsing and the target editor works needs
|
||||
// to be rewritten to handle functions that take multiple series
|
||||
function parseTarget() {
|
||||
$scope.functions = [];
|
||||
$scope.segments = [];
|
||||
delete $scope.parserError;
|
||||
|
||||
if ($scope.target.textEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser($scope.target.target);
|
||||
var astNode = parser.getAst();
|
||||
if (astNode === null) {
|
||||
checkOtherSegments(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (astNode.type === 'error') {
|
||||
$scope.parserError = astNode.message + " at position: " + astNode.pos;
|
||||
$scope.target.textEditor = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
parseTargeRecursive(astNode);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('error parsing target:', err.message);
|
||||
$scope.parserError = err.message;
|
||||
$scope.target.textEditor = true;
|
||||
}
|
||||
|
||||
checkOtherSegments($scope.segments.length - 1);
|
||||
}
|
||||
|
||||
function addFunctionParameter(func, value, index, shiftBack) {
|
||||
if (shiftBack) {
|
||||
index = Math.max(index - 1, 0);
|
||||
}
|
||||
func.params[index] = value;
|
||||
}
|
||||
|
||||
function parseTargeRecursive(astNode, func, index) {
|
||||
if (astNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch(astNode.type) {
|
||||
case 'function':
|
||||
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
|
||||
|
||||
_.each(astNode.params, function(param, index) {
|
||||
parseTargeRecursive(param, innerFunc, index);
|
||||
});
|
||||
|
||||
innerFunc.updateText();
|
||||
$scope.functions.push(innerFunc);
|
||||
break;
|
||||
|
||||
case 'series-ref':
|
||||
addFunctionParameter(func, astNode.value, index, $scope.segments.length > 0);
|
||||
break;
|
||||
case 'bool':
|
||||
case 'string':
|
||||
case 'number':
|
||||
if ((index-1) >= func.def.params.length) {
|
||||
throw { message: 'invalid number of parameters to method ' + func.def.name };
|
||||
}
|
||||
addFunctionParameter(func, astNode.value, index, true);
|
||||
break;
|
||||
case 'metric':
|
||||
if ($scope.segments.length > 0) {
|
||||
if (astNode.segments.length !== 1) {
|
||||
throw { message: 'Multiple metric params not supported, use text editor.' };
|
||||
}
|
||||
addFunctionParameter(func, astNode.segments[0].value, index, true);
|
||||
break;
|
||||
}
|
||||
|
||||
$scope.segments = _.map(astNode.segments, function(segment) {
|
||||
return uiSegmentSrv.newSegment(segment);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSegmentPathUpTo(index) {
|
||||
var arr = $scope.segments.slice(0, index);
|
||||
|
||||
return _.reduce(arr, function(result, segment) {
|
||||
return result ? (result + "." + segment.value) : segment.value;
|
||||
}, "");
|
||||
}
|
||||
|
||||
function checkOtherSegments(fromIndex) {
|
||||
if (fromIndex === 0) {
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
return;
|
||||
}
|
||||
|
||||
var path = getSegmentPathUpTo(fromIndex + 1);
|
||||
return datasource.metricFindQuery(path)
|
||||
.then(function(segments) {
|
||||
if (segments.length === 0) {
|
||||
if (path !== '') {
|
||||
$scope.segments = $scope.segments.splice(0, fromIndex);
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
}
|
||||
} else if (segments[0].expandable) {
|
||||
if ($scope.segments.length === fromIndex) {
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
}
|
||||
else {
|
||||
return checkOtherSegments(fromIndex + 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
});
|
||||
}
|
||||
|
||||
function setSegmentFocus(segmentIndex) {
|
||||
_.each($scope.segments, function(segment, index) {
|
||||
segment.focus = segmentIndex === index;
|
||||
});
|
||||
}
|
||||
|
||||
function wrapFunction(target, func) {
|
||||
return func.render(target);
|
||||
}
|
||||
|
||||
$scope.getAltSegments = function (index) {
|
||||
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
|
||||
|
||||
return datasource.metricFindQuery(query).then(function(segments) {
|
||||
var altSegments = _.map(segments, function(segment) {
|
||||
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (altSegments.length === 0) { return altSegments; }
|
||||
|
||||
// add template variables
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
altSegments.unshift(uiSegmentSrv.newSegment({
|
||||
type: 'template',
|
||||
value: '$' + variable.name,
|
||||
expandable: true,
|
||||
}));
|
||||
});
|
||||
|
||||
// add wildcard option
|
||||
altSegments.unshift(uiSegmentSrv.newSegment('*'));
|
||||
return altSegments;
|
||||
})
|
||||
.then(null, function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.segmentValueChanged = function (segment, segmentIndex) {
|
||||
delete $scope.parserError;
|
||||
|
||||
if ($scope.functions.length > 0 && $scope.functions[0].def.fake) {
|
||||
$scope.functions = [];
|
||||
}
|
||||
|
||||
if (segment.expandable) {
|
||||
return checkOtherSegments(segmentIndex + 1).then(function() {
|
||||
setSegmentFocus(segmentIndex + 1);
|
||||
$scope.targetChanged();
|
||||
});
|
||||
}
|
||||
else {
|
||||
$scope.segments = $scope.segments.splice(0, segmentIndex + 1);
|
||||
}
|
||||
|
||||
setSegmentFocus(segmentIndex + 1);
|
||||
$scope.targetChanged();
|
||||
};
|
||||
|
||||
$scope.targetTextChanged = function() {
|
||||
parseTarget();
|
||||
panelCtrl.refresh();
|
||||
};
|
||||
|
||||
$scope.targetChanged = function() {
|
||||
if ($scope.parserError) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldTarget = $scope.target.target;
|
||||
var target = getSegmentPathUpTo($scope.segments.length);
|
||||
$scope.target.target = _.reduce($scope.functions, wrapFunction, target);
|
||||
|
||||
if ($scope.target.target !== oldTarget) {
|
||||
if ($scope.segments[$scope.segments.length - 1].value !== 'select metric') {
|
||||
panelCtrl.refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeFunction = function(func) {
|
||||
$scope.functions = _.without($scope.functions, func);
|
||||
$scope.targetChanged();
|
||||
};
|
||||
|
||||
$scope.addFunction = function(funcDef) {
|
||||
var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
|
||||
newFunc.added = true;
|
||||
$scope.functions.push(newFunc);
|
||||
|
||||
$scope.moveAliasFuncLast();
|
||||
$scope.smartlyHandleNewAliasByNode(newFunc);
|
||||
|
||||
if ($scope.segments.length === 1 && $scope.segments[0].fake) {
|
||||
$scope.segments = [];
|
||||
}
|
||||
|
||||
if (!newFunc.params.length && newFunc.added) {
|
||||
$scope.targetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveAliasFuncLast = function() {
|
||||
var aliasFunc = _.find($scope.functions, function(func) {
|
||||
return func.def.name === 'alias' ||
|
||||
func.def.name === 'aliasByNode' ||
|
||||
func.def.name === 'aliasByMetric';
|
||||
});
|
||||
|
||||
if (aliasFunc) {
|
||||
$scope.functions = _.without($scope.functions, aliasFunc);
|
||||
$scope.functions.push(aliasFunc);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.smartlyHandleNewAliasByNode = function(func) {
|
||||
if (func.def.name !== 'aliasByNode') {
|
||||
return;
|
||||
}
|
||||
for(var i = 0; i < $scope.segments.length; i++) {
|
||||
if ($scope.segments[i].value.indexOf('*') >= 0) {
|
||||
func.params[0] = i;
|
||||
func.added = false;
|
||||
$scope.targetChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleMetricOptions = function() {
|
||||
$scope.panel.metricOptionsEnabled = !$scope.panel.metricOptionsEnabled;
|
||||
if (!$scope.panel.metricOptionsEnabled) {
|
||||
delete $scope.panel.cacheTimeout;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
275
public/app/plugins/datasource/graphite/query_ctrl.ts
Normal file
275
public/app/plugins/datasource/graphite/query_ctrl.ts
Normal file
@ -0,0 +1,275 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import './add_graphite_func';
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import gfunc from './gfunc';
|
||||
import {Parser} from './parser';
|
||||
import {QueryCtrl} from 'app/features/panel/panel';
|
||||
|
||||
export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'public/app/plugins/datasource/graphite/partials/query.editor.html';
|
||||
|
||||
functions: any[];
|
||||
segments: any[];
|
||||
parserError: string;
|
||||
|
||||
constructor($scope, $injector, private uiSegmentSrv, private templateSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
if (this.target) {
|
||||
this.target.target = this.target.target || '';
|
||||
this.parseTarget();
|
||||
}
|
||||
}
|
||||
|
||||
toggleEditorMode() {
|
||||
this.target.textEditor = !this.target.textEditor;
|
||||
this.parseTarget();
|
||||
}
|
||||
|
||||
parseTarget() {
|
||||
this.functions = [];
|
||||
this.segments = [];
|
||||
delete this.parserError;
|
||||
|
||||
if (this.target.textEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(this.target.target);
|
||||
var astNode = parser.getAst();
|
||||
if (astNode === null) {
|
||||
this.checkOtherSegments(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (astNode.type === 'error') {
|
||||
this.parserError = astNode.message + " at position: " + astNode.pos;
|
||||
this.target.textEditor = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.parseTargeRecursive(astNode, null, 0);
|
||||
} catch (err) {
|
||||
console.log('error parsing target:', err.message);
|
||||
this.parserError = err.message;
|
||||
this.target.textEditor = true;
|
||||
}
|
||||
|
||||
this.checkOtherSegments(this.segments.length - 1);
|
||||
}
|
||||
|
||||
addFunctionParameter(func, value, index, shiftBack) {
|
||||
if (shiftBack) {
|
||||
index = Math.max(index - 1, 0);
|
||||
}
|
||||
func.params[index] = value;
|
||||
}
|
||||
|
||||
parseTargeRecursive(astNode, func, index) {
|
||||
if (astNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (astNode.type) {
|
||||
case 'function':
|
||||
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
|
||||
_.each(astNode.params, (param, index) => {
|
||||
this.parseTargeRecursive(param, innerFunc, index);
|
||||
});
|
||||
|
||||
innerFunc.updateText();
|
||||
this.functions.push(innerFunc);
|
||||
break;
|
||||
case 'series-ref':
|
||||
this.addFunctionParameter(func, astNode.value, index, this.segments.length > 0);
|
||||
break;
|
||||
case 'bool':
|
||||
case 'string':
|
||||
case 'number':
|
||||
if ((index-1) >= func.def.params.length) {
|
||||
throw { message: 'invalid number of parameters to method ' + func.def.name };
|
||||
}
|
||||
this.addFunctionParameter(func, astNode.value, index, true);
|
||||
break;
|
||||
case 'metric':
|
||||
if (this.segments.length > 0) {
|
||||
if (astNode.segments.length !== 1) {
|
||||
throw { message: 'Multiple metric params not supported, use text editor.' };
|
||||
}
|
||||
this.addFunctionParameter(func, astNode.segments[0].value, index, true);
|
||||
break;
|
||||
}
|
||||
|
||||
this.segments = _.map(astNode.segments, segment => {
|
||||
return this.uiSegmentSrv.newSegment(segment);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSegmentPathUpTo(index) {
|
||||
var arr = this.segments.slice(0, index);
|
||||
|
||||
return _.reduce(arr, function(result, segment) {
|
||||
return result ? (result + "." + segment.value) : segment.value;
|
||||
}, "");
|
||||
}
|
||||
|
||||
checkOtherSegments(fromIndex) {
|
||||
if (fromIndex === 0) {
|
||||
this.segments.push(this.uiSegmentSrv.newSelectMetric());
|
||||
return;
|
||||
}
|
||||
|
||||
var path = this.getSegmentPathUpTo(fromIndex + 1);
|
||||
return this.datasource.metricFindQuery(path).then(segments => {
|
||||
if (segments.length === 0) {
|
||||
if (path !== '') {
|
||||
this.segments = this.segments.splice(0, fromIndex);
|
||||
this.segments.push(this.uiSegmentSrv.newSelectMetric());
|
||||
}
|
||||
} else if (segments[0].expandable) {
|
||||
if (this.segments.length === fromIndex) {
|
||||
this.segments.push(this.uiSegmentSrv.newSelectMetric());
|
||||
} else {
|
||||
return this.checkOtherSegments(fromIndex + 1);
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
this.parserError = err.message || 'Failed to issue metric query';
|
||||
});
|
||||
}
|
||||
|
||||
setSegmentFocus(segmentIndex) {
|
||||
_.each(this.segments, (segment, index) => {
|
||||
segment.focus = segmentIndex === index;
|
||||
});
|
||||
}
|
||||
|
||||
wrapFunction(target, func) {
|
||||
return func.render(target);
|
||||
}
|
||||
|
||||
getAltSegments(index) {
|
||||
var query = index === 0 ? '*' : this.getSegmentPathUpTo(index) + '.*';
|
||||
|
||||
return this.datasource.metricFindQuery(query).then(segments => {
|
||||
var altSegments = _.map(segments, segment => {
|
||||
return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (altSegments.length === 0) { return altSegments; }
|
||||
|
||||
// add template variables
|
||||
_.each(this.templateSrv.variables, variable => {
|
||||
altSegments.unshift(this.uiSegmentSrv.newSegment({
|
||||
type: 'template',
|
||||
value: '$' + variable.name,
|
||||
expandable: true,
|
||||
}));
|
||||
});
|
||||
|
||||
// add wildcard option
|
||||
altSegments.unshift(this.uiSegmentSrv.newSegment('*'));
|
||||
return altSegments;
|
||||
}).catch(err => {
|
||||
this.parserError = err.message || 'Failed to issue metric query';
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
segmentValueChanged(segment, segmentIndex) {
|
||||
delete this.parserError;
|
||||
|
||||
if (this.functions.length > 0 && this.functions[0].def.fake) {
|
||||
this.functions = [];
|
||||
}
|
||||
|
||||
if (segment.expandable) {
|
||||
return this.checkOtherSegments(segmentIndex + 1).then(() => {
|
||||
this.setSegmentFocus(segmentIndex + 1);
|
||||
this.targetChanged();
|
||||
});
|
||||
} else {
|
||||
this.segments = this.segments.splice(0, segmentIndex + 1);
|
||||
}
|
||||
|
||||
this.setSegmentFocus(segmentIndex + 1);
|
||||
this.targetChanged();
|
||||
}
|
||||
|
||||
targetTextChanged() {
|
||||
this.parseTarget();
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
|
||||
targetChanged() {
|
||||
if (this.parserError) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldTarget = this.target.target;
|
||||
var target = this.getSegmentPathUpTo(this.segments.length);
|
||||
this.target.target = _.reduce(this.functions, this.wrapFunction, target);
|
||||
|
||||
if (this.target.target !== oldTarget) {
|
||||
if (this.segments[this.segments.length - 1].value !== 'select metric') {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeFunction(func) {
|
||||
this.functions = _.without(this.functions, func);
|
||||
this.targetChanged();
|
||||
}
|
||||
|
||||
addFunction(funcDef) {
|
||||
var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
|
||||
newFunc.added = true;
|
||||
this.functions.push(newFunc);
|
||||
|
||||
this.moveAliasFuncLast();
|
||||
this.smartlyHandleNewAliasByNode(newFunc);
|
||||
|
||||
if (this.segments.length === 1 && this.segments[0].fake) {
|
||||
this.segments = [];
|
||||
}
|
||||
|
||||
if (!newFunc.params.length && newFunc.added) {
|
||||
this.targetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
moveAliasFuncLast() {
|
||||
var aliasFunc = _.find(this.functions, function(func) {
|
||||
return func.def.name === 'alias' ||
|
||||
func.def.name === 'aliasByNode' ||
|
||||
func.def.name === 'aliasByMetric';
|
||||
});
|
||||
|
||||
if (aliasFunc) {
|
||||
this.functions = _.without(this.functions, aliasFunc);
|
||||
this.functions.push(aliasFunc);
|
||||
}
|
||||
}
|
||||
|
||||
smartlyHandleNewAliasByNode(func) {
|
||||
if (func.def.name !== 'aliasByNode') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.segments.length; i++) {
|
||||
if (this.segments[i].value.indexOf('*') >= 0) {
|
||||
func.params[0] = i;
|
||||
func.added = false;
|
||||
this.targetChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import Datasource from "../datasource";
|
||||
import {GraphiteDatasource} from "../datasource";
|
||||
|
||||
describe('graphiteDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
@ -18,7 +18,7 @@ describe('graphiteDatasource', function() {
|
||||
}));
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.ds = ctx.$injector.instantiate(Datasource, {instanceSettings: instanceSettings});
|
||||
ctx.ds = ctx.$injector.instantiate(GraphiteDatasource, {instanceSettings: instanceSettings});
|
||||
});
|
||||
|
||||
describe('When querying influxdb with one target using query editor target spec', function() {
|
||||
|
118
public/app/plugins/datasource/graphite/specs/lexer_specs.ts
Normal file
118
public/app/plugins/datasource/graphite/specs/lexer_specs.ts
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
import {describe, it, expect} from 'test/lib/common';
|
||||
import {Lexer} from '../lexer';
|
||||
|
||||
describe('when lexing graphite expression', function() {
|
||||
|
||||
it('should tokenize metric expression', function() {
|
||||
var lexer = new Lexer('metric.test.*.asd.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('metric');
|
||||
expect(tokens[1].value).to.be('.');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[4].pos).to.be(13);
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with dash', function() {
|
||||
var lexer = new Lexer('metric.test.se1-server-*.asd.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[4].value).to.be('se1-server-*');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with dash2', function() {
|
||||
var lexer = new Lexer('net.192-168-1-1.192-168-1-9.ping_value.*');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('net');
|
||||
expect(tokens[2].value).to.be('192-168-1-1');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with equal sign', function() {
|
||||
var lexer = new Lexer('apps=test');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('apps=test');
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var lexer = new Lexer('offset(test.metric, -100)');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with curly braces', function() {
|
||||
var lexer = new Lexer('metric.se1-{first, second}.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens.length).to.be(10);
|
||||
expect(tokens[3].type).to.be('{');
|
||||
expect(tokens[4].value).to.be('first');
|
||||
expect(tokens[5].value).to.be(',');
|
||||
expect(tokens[6].value).to.be('second');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with number segments', function() {
|
||||
var lexer = new Lexer("metric.10.12_10.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('10');
|
||||
expect(tokens[4].value).to.be('12_10');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize func call with numbered metric and number arg', function() {
|
||||
var lexer = new Lexer("scale(metric.10, 15)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[4].value).to.be('10');
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
});
|
||||
|
||||
it('should tokenize metric with template parameter', function() {
|
||||
var lexer = new Lexer("metric.[[server]].test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('[[server]]');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize metric with question mark', function() {
|
||||
var lexer = new Lexer("metric.server_??.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('server_??');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should handle error with unterminated string', function() {
|
||||
var lexer = new Lexer("alias(metric, 'asd)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('alias');
|
||||
expect(tokens[1].value).to.be('(');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[3].value).to.be(',');
|
||||
expect(tokens[4].type).to.be('string');
|
||||
expect(tokens[4].isUnclosed).to.be(true);
|
||||
expect(tokens[4].pos).to.be(20);
|
||||
});
|
||||
|
||||
it('should handle float parameters', function() {
|
||||
var lexer = new Lexer("alias(metric, 0.002)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[4].value).to.be('0.002');
|
||||
});
|
||||
|
||||
it('should handle bool parameters', function() {
|
||||
var lexer = new Lexer("alias(metric, true, false)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('bool');
|
||||
expect(tokens[4].value).to.be('true');
|
||||
expect(tokens[6].type).to.be('bool');
|
||||
});
|
||||
});
|
183
public/app/plugins/datasource/graphite/specs/parser_specs.ts
Normal file
183
public/app/plugins/datasource/graphite/specs/parser_specs.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import {describe, it, expect} from 'test/lib/common';
|
||||
import {Parser} from '../parser';
|
||||
|
||||
describe('when parsing', function() {
|
||||
|
||||
it('simple metric expression', function() {
|
||||
var parser = new Parser('metric.test.*.asd.count');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(5);
|
||||
expect(rootNode.segments[0].value).to.be('metric');
|
||||
});
|
||||
|
||||
it('simple metric expression with numbers in segments', function() {
|
||||
var parser = new Parser('metric.10.15_20.5');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(4);
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('15_20');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces', function() {
|
||||
var parser = new Parser('metric.se1-{count, max}');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(2);
|
||||
expect(rootNode.segments[1].value).to.be('se1-{count,max}');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces at start of segment and with post chars', function() {
|
||||
var parser = new Parser('metric.{count, max}-something.count');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(3);
|
||||
expect(rootNode.segments[1].value).to.be('{count,max}-something');
|
||||
});
|
||||
|
||||
it('simple function', function() {
|
||||
var parser = new Parser('sum(test)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var parser = new Parser('offset(test.metric, -100)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
});
|
||||
|
||||
it('simple function with string arg', function() {
|
||||
var parser = new Parser("randomWalk('test')");
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
expect(rootNode.params[0].type).to.be('string');
|
||||
});
|
||||
|
||||
it('function with multiple args', function() {
|
||||
var parser = new Parser("sum(test, 1, 'test')");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(3);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
expect(rootNode.params[2].type).to.be('string');
|
||||
});
|
||||
|
||||
it('function with nested function', function() {
|
||||
var parser = new Parser("sum(scaleToSeconds(test, 1))");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
expect(rootNode.params[0].type).to.be('function');
|
||||
expect(rootNode.params[0].name).to.be('scaleToSeconds');
|
||||
expect(rootNode.params[0].params.length).to.be(2);
|
||||
expect(rootNode.params[0].params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].params[1].type).to.be('number');
|
||||
});
|
||||
|
||||
it('function with multiple series', function() {
|
||||
var parser = new Parser("sum(test.test.*.count, test.timers.*.count)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(2);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('metric');
|
||||
});
|
||||
|
||||
it('function with templated series', function() {
|
||||
var parser = new Parser("sum(test.[[server]].count)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be(undefined);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].segments[1].type).to.be('segment');
|
||||
expect(rootNode.params[0].segments[1].value).to.be('[[server]]');
|
||||
});
|
||||
|
||||
it('invalid metric expression', function() {
|
||||
var parser = new Parser('metric.test.*.asd.');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Expected metric identifier instead found end of string');
|
||||
expect(rootNode.pos).to.be(19);
|
||||
});
|
||||
|
||||
it('invalid function expression missing closing parenthesis', function() {
|
||||
var parser = new Parser('sum(test');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Expected closing parenthesis instead found end of string');
|
||||
expect(rootNode.pos).to.be(9);
|
||||
});
|
||||
|
||||
it('unclosed string in function', function() {
|
||||
var parser = new Parser("sum('test)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Unclosed string parameter');
|
||||
expect(rootNode.pos).to.be(11);
|
||||
});
|
||||
|
||||
it('handle issue #69', function() {
|
||||
var parser = new Parser('cactiStyle(offset(scale(net.192-168-1-1.192-168-1-9.ping_value.*,0.001),-100))');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
});
|
||||
|
||||
it('handle float function arguments', function() {
|
||||
var parser = new Parser('scale(test, 0.002)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
expect(rootNode.params[1].value).to.be(0.002);
|
||||
});
|
||||
|
||||
it('handle curly brace pattern at start', function() {
|
||||
var parser = new Parser('{apps}.test');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments[0].value).to.be('{apps}');
|
||||
expect(rootNode.segments[1].value).to.be('test');
|
||||
});
|
||||
|
||||
it('series parameters', function() {
|
||||
var parser = new Parser('asPercent(#A, #B)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('series-ref');
|
||||
expect(rootNode.params[0].value).to.be('#A');
|
||||
expect(rootNode.params[1].value).to.be('#B');
|
||||
});
|
||||
|
||||
it('series parameters, issue 2788', function() {
|
||||
var parser = new Parser("summarize(diffSeries(#A, #B), '10m', 'sum', false)");
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('function');
|
||||
expect(rootNode.params[1].value).to.be('10m');
|
||||
expect(rootNode.params[3].type).to.be('bool');
|
||||
});
|
||||
|
||||
it('should parse metric expression with ip number segments', function() {
|
||||
var parser = new Parser('5.10.123.5');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.segments[0].value).to.be('5');
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('123');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
});
|
@ -5,6 +5,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/co
|
||||
|
||||
import gfunc from '../gfunc';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import {GraphiteQueryCtrl} from '../query_ctrl';
|
||||
|
||||
describe('GraphiteQueryCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
@ -17,53 +18,47 @@ describe('GraphiteQueryCtrl', function() {
|
||||
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
|
||||
ctx.$q = $q;
|
||||
ctx.scope = $rootScope.$new();
|
||||
ctx.scope.ctrl = {panel: ctx.panel};
|
||||
ctx.panelCtrl = ctx.scope.ctrl;
|
||||
ctx.scope.datasource = ctx.datasource;
|
||||
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.controller = $controller('GraphiteQueryCtrl', {$scope: ctx.scope});
|
||||
ctx.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
|
||||
ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
ctx.panelCtrl = {panel: {}};
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
|
||||
ctx.ctrl = $controller(GraphiteQueryCtrl, {$scope: ctx.scope}, {
|
||||
panelCtrl: ctx.panelCtrl,
|
||||
datasource: ctx.datasource,
|
||||
target: ctx.target
|
||||
});
|
||||
ctx.scope.$digest();
|
||||
}));
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.scope.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
|
||||
});
|
||||
|
||||
describe('init', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
it('should validate metric key exists', function() {
|
||||
expect(ctx.scope.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
|
||||
expect(ctx.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
|
||||
});
|
||||
|
||||
it('should delete last segment if no metrics are found', function() {
|
||||
expect(ctx.scope.segments[2].value).to.be('select metric');
|
||||
expect(ctx.ctrl.segments[2].value).to.be('select metric');
|
||||
});
|
||||
|
||||
it('should parse expression and build function model', function() {
|
||||
expect(ctx.scope.functions.length).to.be(2);
|
||||
expect(ctx.ctrl.functions.length).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when adding function', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'test.prod.*.count';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
ctx.scope.addFunction(gfunc.getFuncDef('aliasByNode'));
|
||||
ctx.ctrl.target.target = 'test.prod.*.count';
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.addFunction(gfunc.getFuncDef('aliasByNode'));
|
||||
});
|
||||
|
||||
it('should add function with correct node number', function() {
|
||||
expect(ctx.scope.functions[0].params[0]).to.be(2);
|
||||
expect(ctx.ctrl.functions[0].params[0]).to.be(2);
|
||||
});
|
||||
|
||||
it('should update target', function() {
|
||||
expect(ctx.scope.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
|
||||
expect(ctx.ctrl.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
|
||||
});
|
||||
|
||||
it('should call refresh', function() {
|
||||
@ -73,78 +68,72 @@ describe('GraphiteQueryCtrl', function() {
|
||||
|
||||
describe('when adding function before any metric segment', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = '';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
ctx.scope.addFunction(gfunc.getFuncDef('asPercent'));
|
||||
ctx.ctrl.target.target = '';
|
||||
ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.addFunction(gfunc.getFuncDef('asPercent'));
|
||||
});
|
||||
|
||||
it('should add function and remove select metric link', function() {
|
||||
expect(ctx.scope.segments.length).to.be(0);
|
||||
expect(ctx.ctrl.segments.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when initalizing target without metric expression and only function', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'asPercent(#A, #B)';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.scope.init();
|
||||
ctx.ctrl.target.target = 'asPercent(#A, #B)';
|
||||
ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.scope.$digest();
|
||||
});
|
||||
|
||||
it('should not add select metric segment', function() {
|
||||
expect(ctx.scope.segments.length).to.be(0);
|
||||
expect(ctx.ctrl.segments.length).to.be(0);
|
||||
});
|
||||
|
||||
it('should add both series refs as params', function() {
|
||||
expect(ctx.scope.functions[0].params.length).to.be(2);
|
||||
expect(ctx.ctrl.functions[0].params.length).to.be(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when initializing a target with single param func using variable', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'movingAverage(prod.count, $var)';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
ctx.ctrl.target.target = 'movingAverage(prod.count, $var)';
|
||||
ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.ctrl.parseTarget();
|
||||
});
|
||||
|
||||
it('should add 2 segments', function() {
|
||||
expect(ctx.scope.segments.length).to.be(2);
|
||||
expect(ctx.ctrl.segments.length).to.be(2);
|
||||
});
|
||||
|
||||
it('should add function param', function() {
|
||||
expect(ctx.scope.functions[0].params.length).to.be(1);
|
||||
expect(ctx.ctrl.functions[0].params.length).to.be(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when initalizing target without metric expression and function with series-ref', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'asPercent(metric.node.count, #A)';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
ctx.scope.$parent = { get_data: sinon.spy() };
|
||||
ctx.ctrl.target.target = 'asPercent(metric.node.count, #A)';
|
||||
ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.ctrl.parseTarget();
|
||||
});
|
||||
|
||||
it('should add segments', function() {
|
||||
expect(ctx.scope.segments.length).to.be(3);
|
||||
expect(ctx.ctrl.segments.length).to.be(3);
|
||||
});
|
||||
|
||||
it('should have correct func params', function() {
|
||||
expect(ctx.scope.functions[0].params.length).to.be(1);
|
||||
expect(ctx.ctrl.functions[0].params.length).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getting altSegments and metricFindQuery retuns empty array', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.target.target = 'test.count';
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.getAltSegments(1).then(function(results) {
|
||||
ctx.ctrl.target.target = 'test.count';
|
||||
ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.getAltSegments(1).then(function(results) {
|
||||
ctx.altSegments = results;
|
||||
});
|
||||
ctx.scope.$digest();
|
||||
@ -153,22 +142,18 @@ describe('GraphiteQueryCtrl', function() {
|
||||
it('should have no segments', function() {
|
||||
expect(ctx.altSegments.length).to.be(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('targetChanged', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.scope.init();
|
||||
ctx.scope.$digest();
|
||||
|
||||
ctx.panelCtrl.refresh = sinon.spy();
|
||||
ctx.scope.target.target = '';
|
||||
ctx.scope.targetChanged();
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.target.target = '';
|
||||
ctx.ctrl.targetChanged();
|
||||
});
|
||||
|
||||
it('should rebuld target after expression model', function() {
|
||||
expect(ctx.scope.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
|
||||
expect(ctx.ctrl.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
|
||||
});
|
||||
|
||||
it('should call panelCtrl.refresh', function() {
|
||||
|
@ -9,7 +9,7 @@ import * as dateMath from 'app/core/utils/datemath';
|
||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
||||
|
||||
/** @ngInject */
|
||||
function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
this.type = 'prometheus';
|
||||
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
|
||||
this.name = instanceSettings.name;
|
||||
@ -271,8 +271,6 @@ function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv) {
|
||||
if (_.isString(date)) {
|
||||
date = dateMath.parse(date, roundUp);
|
||||
}
|
||||
return Math.floor(date.valueOf() / 1000);
|
||||
return Math.ceil(date.valueOf() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
export {PrometheusDatasource};
|
||||
|
@ -1,23 +1,12 @@
|
||||
import {PrometheusDatasource} from './datasource';
|
||||
import {PrometheusQueryCtrl} from './query_ctrl';
|
||||
|
||||
|
||||
|
||||
|
||||
// function metricsQueryEditor() {
|
||||
// return {controller: 'PrometheusQueryCtrl', templateUrl: 'public/app/plugins/datasource/prometheus/partials/query.editor.html'};
|
||||
// }
|
||||
//
|
||||
// function configView() {
|
||||
// return {templateUrl: ''};
|
||||
// }
|
||||
|
||||
class PrometheusConfigViewCtrl {
|
||||
static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html';
|
||||
}
|
||||
|
||||
export {
|
||||
PrometheusDatasource as Datasource,
|
||||
PrometheusQueryCtrl as MetricsQueryEditor,
|
||||
PrometheusQueryCtrl as QueryCtrl,
|
||||
PrometheusConfigViewCtrl as ConfigView
|
||||
};
|
||||
|
@ -5,10 +5,9 @@ import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import {QueryEditorCtrl} from 'app/features/panel/panel';
|
||||
import {QueryCtrl} from 'app/features/panel/panel';
|
||||
|
||||
/** @ngInject */
|
||||
class PrometheusQueryCtrl extends QueryEditorCtrl {
|
||||
class PrometheusQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'public/app/plugins/datasource/prometheus/partials/query.editor.html';
|
||||
metric: any;
|
||||
resolutions: any;
|
||||
@ -16,6 +15,7 @@ class PrometheusQueryCtrl extends QueryEditorCtrl {
|
||||
suggestMetrics: any;
|
||||
linkToPrometheus: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $injector, private templateSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
import moment from 'moment';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import Datasource from '../datasource';
|
||||
import {PrometheusDatasource} from '../datasource';
|
||||
|
||||
describe('PrometheusDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
@ -13,7 +13,7 @@ describe('PrometheusDatasource', function() {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
ctx.$rootScope = $rootScope;
|
||||
ctx.ds = $injector.instantiate(Datasource, {instanceSettings: instanceSettings});
|
||||
ctx.ds = $injector.instantiate(PrometheusDatasource, {instanceSettings: instanceSettings});
|
||||
}));
|
||||
|
||||
describe('When querying prometheus with one target using query editor target spec', function() {
|
||||
|
@ -47,39 +47,6 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDataQueryTo', function() {
|
||||
var dashboard, panel;
|
||||
|
||||
beforeEach(function() {
|
||||
panel = {targets:[]};
|
||||
dashboard = _dashboardSrv.create({});
|
||||
dashboard.rows.push({panels: [panel]});
|
||||
});
|
||||
|
||||
it('should add target', function() {
|
||||
dashboard.addDataQueryTo(panel);
|
||||
expect(panel.targets.length).to.be(1);
|
||||
});
|
||||
|
||||
it('should set refId', function() {
|
||||
dashboard.addDataQueryTo(panel);
|
||||
expect(panel.targets[0].refId).to.be('A');
|
||||
});
|
||||
|
||||
it('should set refId to first available letter', function() {
|
||||
panel.targets = [{refId: 'A'}];
|
||||
dashboard.addDataQueryTo(panel);
|
||||
expect(panel.targets[1].refId).to.be('B');
|
||||
});
|
||||
|
||||
it('duplicate should get unique refId', function() {
|
||||
panel.targets = [{refId: 'A'}];
|
||||
dashboard.duplicateDataQuery(panel, panel.targets[0]);
|
||||
expect(panel.targets[1].refId).to.be('B');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('row and panel manipulation', function() {
|
||||
var dashboard;
|
||||
|
||||
|
@ -1,122 +0,0 @@
|
||||
define([
|
||||
'app/plugins/datasource/graphite/lexer'
|
||||
], function(Lexer) {
|
||||
'use strict';
|
||||
|
||||
describe('when lexing graphite expression', function() {
|
||||
|
||||
it('should tokenize metric expression', function() {
|
||||
var lexer = new Lexer('metric.test.*.asd.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('metric');
|
||||
expect(tokens[1].value).to.be('.');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[4].pos).to.be(13);
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with dash', function() {
|
||||
var lexer = new Lexer('metric.test.se1-server-*.asd.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[4].value).to.be('se1-server-*');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with dash2', function() {
|
||||
var lexer = new Lexer('net.192-168-1-1.192-168-1-9.ping_value.*');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('net');
|
||||
expect(tokens[2].value).to.be('192-168-1-1');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with equal sign', function() {
|
||||
var lexer = new Lexer('apps=test');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('apps=test');
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var lexer = new Lexer('offset(test.metric, -100)');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with curly braces', function() {
|
||||
var lexer = new Lexer('metric.se1-{first, second}.count');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens.length).to.be(10);
|
||||
expect(tokens[3].type).to.be('{');
|
||||
expect(tokens[4].value).to.be('first');
|
||||
expect(tokens[5].value).to.be(',');
|
||||
expect(tokens[6].value).to.be('second');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with number segments', function() {
|
||||
var lexer = new Lexer("metric.10.12_10.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('10');
|
||||
expect(tokens[4].value).to.be('12_10');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize func call with numbered metric and number arg', function() {
|
||||
var lexer = new Lexer("scale(metric.10, 15)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[4].value).to.be('10');
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
});
|
||||
|
||||
it('should tokenize metric with template parameter', function() {
|
||||
var lexer = new Lexer("metric.[[server]].test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('[[server]]');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize metric with question mark', function() {
|
||||
var lexer = new Lexer("metric.server_??.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('server_??');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should handle error with unterminated string', function() {
|
||||
var lexer = new Lexer("alias(metric, 'asd)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('alias');
|
||||
expect(tokens[1].value).to.be('(');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[3].value).to.be(',');
|
||||
expect(tokens[4].type).to.be('string');
|
||||
expect(tokens[4].isUnclosed).to.be(true);
|
||||
expect(tokens[4].pos).to.be(20);
|
||||
});
|
||||
|
||||
it('should handle float parameters', function() {
|
||||
var lexer = new Lexer("alias(metric, 0.002)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[4].value).to.be('0.002');
|
||||
});
|
||||
|
||||
it('should handle bool parameters', function() {
|
||||
var lexer = new Lexer("alias(metric, true, false)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[4].type).to.be('bool');
|
||||
expect(tokens[4].value).to.be('true');
|
||||
expect(tokens[6].type).to.be('bool');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,188 +0,0 @@
|
||||
define([
|
||||
'app/plugins/datasource/graphite/parser'
|
||||
], function(Parser) {
|
||||
'use strict';
|
||||
|
||||
describe('when parsing', function() {
|
||||
|
||||
it('simple metric expression', function() {
|
||||
var parser = new Parser('metric.test.*.asd.count');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(5);
|
||||
expect(rootNode.segments[0].value).to.be('metric');
|
||||
});
|
||||
|
||||
it('simple metric expression with numbers in segments', function() {
|
||||
var parser = new Parser('metric.10.15_20.5');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(4);
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('15_20');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces', function() {
|
||||
var parser = new Parser('metric.se1-{count, max}');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(2);
|
||||
expect(rootNode.segments[1].value).to.be('se1-{count,max}');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces at start of segment and with post chars', function() {
|
||||
var parser = new Parser('metric.{count, max}-something.count');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(3);
|
||||
expect(rootNode.segments[1].value).to.be('{count,max}-something');
|
||||
});
|
||||
|
||||
it('simple function', function() {
|
||||
var parser = new Parser('sum(test)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var parser = new Parser('offset(test.metric, -100)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
});
|
||||
|
||||
it('simple function with string arg', function() {
|
||||
var parser = new Parser("randomWalk('test')");
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
expect(rootNode.params[0].type).to.be('string');
|
||||
});
|
||||
|
||||
it('function with multiple args', function() {
|
||||
var parser = new Parser("sum(test, 1, 'test')");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(3);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
expect(rootNode.params[2].type).to.be('string');
|
||||
});
|
||||
|
||||
it('function with nested function', function() {
|
||||
var parser = new Parser("sum(scaleToSeconds(test, 1))");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
expect(rootNode.params[0].type).to.be('function');
|
||||
expect(rootNode.params[0].name).to.be('scaleToSeconds');
|
||||
expect(rootNode.params[0].params.length).to.be(2);
|
||||
expect(rootNode.params[0].params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].params[1].type).to.be('number');
|
||||
});
|
||||
|
||||
it('function with multiple series', function() {
|
||||
var parser = new Parser("sum(test.test.*.count, test.timers.*.count)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(2);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('metric');
|
||||
});
|
||||
|
||||
it('function with templated series', function() {
|
||||
var parser = new Parser("sum(test.[[server]].count)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be(undefined);
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[0].segments[1].type).to.be('segment');
|
||||
expect(rootNode.params[0].segments[1].value).to.be('[[server]]');
|
||||
});
|
||||
|
||||
it('invalid metric expression', function() {
|
||||
var parser = new Parser('metric.test.*.asd.');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Expected metric identifier instead found end of string');
|
||||
expect(rootNode.pos).to.be(19);
|
||||
});
|
||||
|
||||
it('invalid function expression missing closing parenthesis', function() {
|
||||
var parser = new Parser('sum(test');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Expected closing parenthesis instead found end of string');
|
||||
expect(rootNode.pos).to.be(9);
|
||||
});
|
||||
|
||||
it('unclosed string in function', function() {
|
||||
var parser = new Parser("sum('test)");
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.message).to.be('Unclosed string parameter');
|
||||
expect(rootNode.pos).to.be(11);
|
||||
});
|
||||
|
||||
it('handle issue #69', function() {
|
||||
var parser = new Parser('cactiStyle(offset(scale(net.192-168-1-1.192-168-1-9.ping_value.*,0.001),-100))');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
});
|
||||
|
||||
it('handle float function arguments', function() {
|
||||
var parser = new Parser('scale(test, 0.002)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
expect(rootNode.params[1].value).to.be(0.002);
|
||||
});
|
||||
|
||||
it('handle curly brace pattern at start', function() {
|
||||
var parser = new Parser('{apps}.test');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments[0].value).to.be('{apps}');
|
||||
expect(rootNode.segments[1].value).to.be('test');
|
||||
});
|
||||
|
||||
it('series parameters', function() {
|
||||
var parser = new Parser('asPercent(#A, #B)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('series-ref');
|
||||
expect(rootNode.params[0].value).to.be('#A');
|
||||
expect(rootNode.params[1].value).to.be('#B');
|
||||
});
|
||||
|
||||
it('series parameters, issue 2788', function() {
|
||||
var parser = new Parser("summarize(diffSeries(#A, #B), '10m', 'sum', false)");
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('function');
|
||||
expect(rootNode.params[1].value).to.be('10m');
|
||||
expect(rootNode.params[3].type).to.be('bool');
|
||||
});
|
||||
|
||||
it('should parse metric expression with ip number segments', function() {
|
||||
var parser = new Parser('5.10.123.5');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.segments[0].value).to.be('5');
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('123');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -28,7 +28,7 @@
|
||||
"no-inferrable-types": true,
|
||||
"no-shadowed-variable": false,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-switch-case-fall-through": false,
|
||||
"no-trailing-comma": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": false,
|
||||
|
Loading…
Reference in New Issue
Block a user