feat(plugins): migrating graphite query editor to new model

This commit is contained in:
Torkel Ödegaard 2016-02-02 12:52:43 +01:00
parent efdd4a6682
commit 822c8f1575
29 changed files with 1946 additions and 2035 deletions

View File

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

View File

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

View File

@ -3,6 +3,6 @@ define([
'./panel_directive',
'./solo_panel_ctrl',
'./panel_loader',
'./query_editor',
'./query_ctrl',
'./panel_editor_tab',
], function () {});

View File

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

View File

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

View File

@ -1,3 +0,0 @@
declare var Datasource: any;
export default Datasource;

View File

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

View 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;
};
}

View File

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

View 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
};
},
};

View File

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

View 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
// };
// });

View File

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

View 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));
},
};

View File

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

View File

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

View 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;
}
}
}
}

View File

@ -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() {

View 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');
});
});

View 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');
});
});

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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