mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #9208 from mtanda/prometheus_ace_complete_improve_label_name_complete
(prometheus) support label name/value completion
This commit is contained in:
commit
8e4efeeece
@ -1,32 +1,80 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import {PrometheusDatasource} from "./datasource";
|
||||
import _ from 'lodash';
|
||||
|
||||
export class PromCompleter {
|
||||
labelQueryCache: any;
|
||||
labelNameCache: any;
|
||||
labelValueCache: any;
|
||||
|
||||
identifierRegexps = [/[\[\]a-zA-Z_0-9=]/];
|
||||
|
||||
constructor(private datasource: PrometheusDatasource) {
|
||||
this.labelQueryCache = {};
|
||||
this.labelNameCache = {};
|
||||
this.labelValueCache = {};
|
||||
}
|
||||
|
||||
getCompletions(editor, session, pos, prefix, callback) {
|
||||
let token = session.getTokenAt(pos.row, pos.column);
|
||||
|
||||
var metricName;
|
||||
switch (token.type) {
|
||||
case 'label.name':
|
||||
callback(null, ['instance', 'job'].map(function (key) {
|
||||
return {
|
||||
caption: key,
|
||||
value: key,
|
||||
meta: "label name",
|
||||
score: Number.MAX_VALUE
|
||||
};
|
||||
}));
|
||||
case 'entity.name.tag':
|
||||
metricName = this.findMetricName(session, pos.row, pos.column);
|
||||
if (!metricName) {
|
||||
callback(null, this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
|
||||
return;
|
||||
case 'label.value':
|
||||
}
|
||||
|
||||
if (this.labelNameCache[metricName]) {
|
||||
callback(null, this.labelNameCache[metricName]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getLabelNameAndValueForMetric(metricName).then(result => {
|
||||
var labelNames = this.transformToCompletions(
|
||||
_.uniq(_.flatten(result.map(r => {
|
||||
return Object.keys(r.metric);
|
||||
})))
|
||||
, 'label name');
|
||||
this.labelNameCache[metricName] = labelNames;
|
||||
callback(null, labelNames);
|
||||
});
|
||||
return;
|
||||
case 'string.quoted':
|
||||
metricName = this.findMetricName(session, pos.row, pos.column);
|
||||
if (!metricName) {
|
||||
callback(null, []);
|
||||
return;
|
||||
}
|
||||
|
||||
var labelNameToken = this.findToken(session, pos.row, pos.column, 'entity.name.tag', null, 'paren.lparen');
|
||||
if (!labelNameToken) {
|
||||
callback(null, []);
|
||||
return;
|
||||
}
|
||||
var labelName = labelNameToken.value;
|
||||
|
||||
if (this.labelValueCache[metricName] && this.labelValueCache[metricName][labelName]) {
|
||||
callback(null, this.labelValueCache[metricName][labelName]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getLabelNameAndValueForMetric(metricName).then(result => {
|
||||
var labelValues = this.transformToCompletions(
|
||||
_.uniq(result.map(r => {
|
||||
return r.metric[labelName];
|
||||
}))
|
||||
, 'label value');
|
||||
this.labelValueCache[metricName] = this.labelValueCache[metricName] || {};
|
||||
this.labelValueCache[metricName][labelName] = labelValues;
|
||||
callback(null, labelValues);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix === '[') {
|
||||
var vectors = [];
|
||||
for (let unit of ['s', 'm', 'h']) {
|
||||
@ -56,4 +104,87 @@ export class PromCompleter {
|
||||
});
|
||||
}
|
||||
|
||||
getLabelNameAndValueForMetric(metricName) {
|
||||
if (this.labelQueryCache[metricName]) {
|
||||
return Promise.resolve(this.labelQueryCache[metricName]);
|
||||
}
|
||||
var op = '=~';
|
||||
if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(metricName)) {
|
||||
op = '=';
|
||||
}
|
||||
var expr = '{__name__' + op + '"' + metricName + '"}';
|
||||
return this.datasource.performInstantQuery({ expr: expr }, new Date().getTime() / 1000).then(response => {
|
||||
this.labelQueryCache[metricName] = response.data.data.result;
|
||||
return response.data.data.result;
|
||||
});
|
||||
}
|
||||
|
||||
transformToCompletions(words, meta) {
|
||||
return words.map(name => {
|
||||
return {
|
||||
caption: name,
|
||||
value: name,
|
||||
meta: meta,
|
||||
score: Number.MAX_VALUE
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
findMetricName(session, row, column) {
|
||||
var metricName = '';
|
||||
|
||||
var tokens;
|
||||
var nameLabelNameToken = this.findToken(session, row, column, 'entity.name.tag', '__name__', 'paren.lparen');
|
||||
if (nameLabelNameToken) {
|
||||
tokens = session.getTokens(nameLabelNameToken.row);
|
||||
var nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
|
||||
if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted') {
|
||||
metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
|
||||
}
|
||||
} else {
|
||||
var metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
|
||||
if (metricNameToken) {
|
||||
tokens = session.getTokens(metricNameToken.row);
|
||||
if (tokens[metricNameToken.index + 1].type === 'paren.lparen') {
|
||||
metricName = metricNameToken.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metricName;
|
||||
}
|
||||
|
||||
findToken(session, row, column, target, value, guard) {
|
||||
var tokens, idx;
|
||||
for (var r = row; r >= 0; r--) {
|
||||
tokens = session.getTokens(r);
|
||||
if (r === row) { // current row
|
||||
var c = 0;
|
||||
for (idx = 0; idx < tokens.length; idx++) {
|
||||
c += tokens[idx].value.length;
|
||||
if (c >= column) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idx = tokens.length - 1;
|
||||
}
|
||||
|
||||
for (; idx >= 0; idx--) {
|
||||
if (tokens[idx].type === guard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tokens[idx].type === target
|
||||
&& (!value || tokens[idx].value === value)) {
|
||||
tokens[idx].row = r;
|
||||
tokens[idx].index = idx;
|
||||
return tokens[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ var PrometheusHighlightRules = function() {
|
||||
regex : "\\s+"
|
||||
} ],
|
||||
"start-label-matcher" : [ {
|
||||
token : "keyword",
|
||||
token : "entity.name.tag",
|
||||
regex : '[a-zA-Z_][a-zA-Z0-9_]*'
|
||||
}, {
|
||||
token : "keyword.operator",
|
||||
regex : '=~|=|!~|!='
|
||||
}, {
|
||||
token : "string",
|
||||
token : "string.quoted",
|
||||
regex : '"[^"]*"|\'[^\']*\''
|
||||
}, {
|
||||
token : "punctuation.operator",
|
||||
@ -401,7 +401,7 @@ var PrometheusCompletions = function() {};
|
||||
(function() {
|
||||
this.getCompletions = function(state, session, pos, prefix, callback) {
|
||||
var token = session.getTokenAt(pos.row, pos.column);
|
||||
if (token.type === 'label.name' || token.type === 'label.value') {
|
||||
if (token.type === 'entity.name.tag' || token.type === 'string.quoted') {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
|
@ -5,23 +5,110 @@ import {PrometheusDatasource} from '../datasource';
|
||||
|
||||
describe('Prometheus editor completer', function() {
|
||||
|
||||
let editor = {};
|
||||
let session = {
|
||||
getTokenAt: sinon.stub().returns({}),
|
||||
getLine: sinon.stub().returns(""),
|
||||
let sessionData = {
|
||||
currentToken: {},
|
||||
tokens: [],
|
||||
line: ''
|
||||
};
|
||||
let session = {
|
||||
getTokenAt: sinon.stub().returns(sessionData.currentToken),
|
||||
getTokens: sinon.stub().returns(sessionData.tokens),
|
||||
getLine: sinon.stub().returns(sessionData.line),
|
||||
};
|
||||
let editor = { session: session };
|
||||
|
||||
let datasourceStub = <PrometheusDatasource>{};
|
||||
let datasourceStub = <PrometheusDatasource>{
|
||||
performInstantQuery: sinon.stub().withArgs({ expr: '{__name__="node_cpu"' }).returns(Promise.resolve(
|
||||
[
|
||||
{
|
||||
metric: {
|
||||
job: 'node',
|
||||
instance: 'localhost:9100'
|
||||
}
|
||||
}
|
||||
]
|
||||
)),
|
||||
performSuggestQuery: sinon.stub().withArgs('node', true).returns(Promise.resolve(
|
||||
[
|
||||
'node_cpu'
|
||||
]
|
||||
))
|
||||
};
|
||||
let completer = new PromCompleter(datasourceStub);
|
||||
|
||||
describe("When inside brackets", () => {
|
||||
|
||||
it("Should return range vectors", () => {
|
||||
completer.getCompletions(editor, session, 10, "[", (s, res) => {
|
||||
completer.getCompletions(editor, session, { row: 0, column: 10 }, '[', (s, res) => {
|
||||
expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("When inside label matcher, and located at label name", () => {
|
||||
sessionData = {
|
||||
currentToken: { type: 'entity.name.tag', value: 'j', index: 2, start: 9 },
|
||||
tokens: [
|
||||
{ type: 'identifier', value: 'node_cpu' },
|
||||
{ type: 'paren.lparen', value: '{' },
|
||||
{ type: 'entity.name.tag', value: 'j', index: 2, start: 9 },
|
||||
{ type: 'paren.rparen', value: '}' }
|
||||
],
|
||||
line: 'node_cpu{j}'
|
||||
};
|
||||
|
||||
it("Should return label name list", () => {
|
||||
completer.getCompletions(editor, session, { row: 0, column: 10 }, 'j', (s, res) => {
|
||||
expect(res[0]).to.eql({caption: 'job', value: 'job', meta: 'label name'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("When inside label matcher, and located at label name with __name__ match", () => {
|
||||
sessionData = {
|
||||
currentToken: { type: 'entity.name.tag', value: 'j', index: 5, start: 22 },
|
||||
tokens: [
|
||||
{ type: 'paren.lparen', value: '{' },
|
||||
{ type: 'entity.name.tag', value: '__name__' },
|
||||
{ type: 'keyword.operator', value: '=~' },
|
||||
{ type: 'string.quoted', value: '"node_cpu"' },
|
||||
{ type: 'punctuation.operator', value: ',' },
|
||||
{ type: 'entity.name.tag', value: 'j', 'index': 5, 'start': 22 },
|
||||
{ type: 'paren.rparen', value: '}' }
|
||||
],
|
||||
line: '{__name__=~"node_cpu",j}'
|
||||
};
|
||||
|
||||
it("Should return label name list", () => {
|
||||
completer.getCompletions(editor, session, { row: 0, column: 23 }, 'j', (s, res) => {
|
||||
expect(res[0]).to.eql({caption: 'job', value: 'job', meta: 'label name'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("When inside label matcher, and located at label value", () => {
|
||||
sessionData = {
|
||||
currentToken: { type: 'string.quoted', value: '"n"', index: 4, start: 13 },
|
||||
tokens: [
|
||||
{ type: 'identifier', value: 'node_cpu' },
|
||||
{ type: 'paren.lparen', value: '{' },
|
||||
{ type: 'entity.name.tag', value: 'job' },
|
||||
{ type: 'keyword.operator', value: '=' },
|
||||
{ type: 'string.quoted', value: '"n"', index: 4, start: 13 },
|
||||
{ type: 'paren.rparen', value: '}' }
|
||||
],
|
||||
line: 'node_cpu{job="n"}'
|
||||
};
|
||||
|
||||
it("Should return label value list", () => {
|
||||
completer.getCompletions(editor, session, { row: 0, column: 15 }, 'n', (s, res) => {
|
||||
expect(res[0]).to.eql({caption: 'node', value: 'node', meta: 'label value'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user