mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'graphite-seriesbytag' of https://github.com/alexanderzobnin/grafana into alexanderzobnin-graphite-seriesbytag
This commit is contained in:
@@ -217,6 +217,60 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
|
||||
});
|
||||
};
|
||||
|
||||
this.getTags = function(optionalOptions) {
|
||||
let options = optionalOptions || {};
|
||||
|
||||
let httpOptions: any = {
|
||||
method: 'GET',
|
||||
url: '/tags',
|
||||
// for cancellations
|
||||
requestId: options.requestId,
|
||||
};
|
||||
|
||||
if (options && options.range) {
|
||||
httpOptions.params.from = this.translateTime(options.range.from, false);
|
||||
httpOptions.params.until = this.translateTime(options.range.to, true);
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).then(results => {
|
||||
return _.map(results.data, tag => {
|
||||
return {
|
||||
text: tag.tag,
|
||||
id: tag.id
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.getTagValues = function(tag, optionalOptions) {
|
||||
let options = optionalOptions || {};
|
||||
|
||||
let httpOptions: any = {
|
||||
method: 'GET',
|
||||
url: '/tags/' + tag,
|
||||
// for cancellations
|
||||
requestId: options.requestId,
|
||||
};
|
||||
|
||||
if (options && options.range) {
|
||||
httpOptions.params.from = this.translateTime(options.range.from, false);
|
||||
httpOptions.params.until = this.translateTime(options.range.to, true);
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).then(results => {
|
||||
if (results.data && results.data.values) {
|
||||
return _.map(results.data.values, value => {
|
||||
return {
|
||||
text: value.value,
|
||||
id: value.id
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function () {
|
||||
return { status: "success", message: "Data source is working"};
|
||||
|
||||
@@ -6,12 +6,38 @@
|
||||
|
||||
<div ng-hide="ctrl.target.textEditor">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form" ng-if="ctrl.seriesByTagUsed">
|
||||
<label class="gf-form-label query-keyword">seriesByTag</label>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="tag in ctrl.tags" class="gf-form">
|
||||
<gf-form-dropdown model="tag.key" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-key"
|
||||
get-options="ctrl.getTags()"
|
||||
on-change="ctrl.tagChanged(tag, $index)">
|
||||
</gf-form-dropdown>
|
||||
<gf-form-dropdown model="tag.operator" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-operator"
|
||||
get-options="ctrl.getTagOperators()"
|
||||
on-change="ctrl.tagChanged(tag, $index)">
|
||||
</gf-form-dropdown>
|
||||
<gf-form-dropdown model="tag.value" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-value"
|
||||
get-options="ctrl.getTagValues(tag)"
|
||||
on-change="ctrl.tagChanged(tag, $index)">
|
||||
</gf-form-dropdown>
|
||||
<label class="gf-form-label query-keyword" ng-if="ctrl.showDelimiter($index)">,</label>
|
||||
</div>
|
||||
<div ng-if="ctrl.seriesByTagUsed" ng-repeat="segment in ctrl.addTagSegments" role="menuitem" class="gf-form">
|
||||
<metric-segment segment="segment"
|
||||
get-options="ctrl.getTagsAsSegments()"
|
||||
on-change="ctrl.addNewTag(segment)">
|
||||
</metric-segment>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="segment in ctrl.segments" role="menuitem" class="gf-form">
|
||||
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="func in ctrl.functions" class="gf-form">
|
||||
<span graphite-func-editor class="gf-form-label query-part"></span>
|
||||
<span graphite-func-editor class="gf-form-label query-part" ng-hide="ctrl.getSeriesByTagFuncIndex() === $index"></span>
|
||||
</div>
|
||||
|
||||
<div class="gf-form dropdown">
|
||||
|
||||
@@ -7,11 +7,17 @@ import {Parser} from './parser';
|
||||
import {QueryCtrl} from 'app/plugins/sdk';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
const GRAPHITE_TAG_OPERATORS = ['=', '!=', '=~', '!=~'];
|
||||
|
||||
export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
functions: any[];
|
||||
segments: any[];
|
||||
addTagSegments: any[];
|
||||
tags: any[];
|
||||
seriesByTagUsed: boolean;
|
||||
removeTagValue: string;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor($scope, $injector, private uiSegmentSrv, private templateSrv) {
|
||||
@@ -21,6 +27,8 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
this.target.target = this.target.target || '';
|
||||
this.parseTarget();
|
||||
}
|
||||
|
||||
this.removeTagValue = '-- remove tag --';
|
||||
}
|
||||
|
||||
toggleEditorMode() {
|
||||
@@ -59,6 +67,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
this.checkOtherSegments(this.segments.length - 1);
|
||||
this.checkForSeriesByTag();
|
||||
}
|
||||
|
||||
addFunctionParameter(func, value, index, shiftBack) {
|
||||
@@ -304,6 +313,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
if (!newFunc.params.length && newFunc.added) {
|
||||
this.targetChanged();
|
||||
}
|
||||
|
||||
if (newFunc.def.name === 'seriesByTag') {
|
||||
this.parseTarget();
|
||||
}
|
||||
}
|
||||
|
||||
moveAliasFuncLast() {
|
||||
@@ -333,4 +346,126 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Graphite seriesByTag support //
|
||||
//////////////////////////////////
|
||||
|
||||
checkForSeriesByTag() {
|
||||
let seriesByTagFunc = _.find(this.functions, (func) => func.def.name === 'seriesByTag');
|
||||
if (seriesByTagFunc) {
|
||||
this.seriesByTagUsed = true;
|
||||
let tags = this.splitSeriesByTagParams(seriesByTagFunc);
|
||||
this.tags = tags;
|
||||
this.fixTagSegments();
|
||||
}
|
||||
}
|
||||
|
||||
splitSeriesByTagParams(func) {
|
||||
const tagPattern = /([^\!=~]+)([\!=~]+)([^\!=~]+)/;
|
||||
return _.flatten(_.map(func.params, (param: string) => {
|
||||
let matches = tagPattern.exec(param);
|
||||
if (matches) {
|
||||
let tag = matches.slice(1);
|
||||
if (tag.length === 3) {
|
||||
return {
|
||||
key: tag[0],
|
||||
operator: tag[1],
|
||||
value: tag[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}));
|
||||
}
|
||||
|
||||
getTags() {
|
||||
return this.datasource.getTags().then((values) => {
|
||||
let altTags = _.map(values, 'text');
|
||||
altTags.splice(0, 0, this.removeTagValue);
|
||||
return mapToDropdownOptions(altTags);
|
||||
});
|
||||
}
|
||||
|
||||
getTagsAsSegments() {
|
||||
return this.datasource.getTags().then((values) => {
|
||||
return _.map(values, (val) => {
|
||||
return this.uiSegmentSrv.newSegment(val.text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getTagOperators() {
|
||||
return mapToDropdownOptions(GRAPHITE_TAG_OPERATORS);
|
||||
}
|
||||
|
||||
getTagValues(tag) {
|
||||
let tagKey = tag.key;
|
||||
return this.datasource.getTagValues(tagKey).then((values) => {
|
||||
let altValues = _.map(values, 'text');
|
||||
return mapToDropdownOptions(altValues);
|
||||
});
|
||||
}
|
||||
|
||||
tagChanged(tag, tagIndex) {
|
||||
this.error = null;
|
||||
|
||||
if (tag.key === this.removeTagValue) {
|
||||
this.removeTag(tagIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
let newTagParam = renderTagString(tag);
|
||||
this.getSeriesByTagFunc().params[tagIndex] = newTagParam;
|
||||
this.tags[tagIndex] = tag;
|
||||
this.targetChanged();
|
||||
}
|
||||
|
||||
getSeriesByTagFuncIndex() {
|
||||
return _.findIndex(this.functions, (func) => func.def.name === 'seriesByTag');
|
||||
}
|
||||
|
||||
getSeriesByTagFunc() {
|
||||
let seriesByTagFuncIndex = this.getSeriesByTagFuncIndex();
|
||||
if (seriesByTagFuncIndex >= 0) {
|
||||
return this.functions[seriesByTagFuncIndex];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
addNewTag(segment) {
|
||||
let newTagKey = segment.value;
|
||||
let newTag = {key: newTagKey, operator: '=', value: 'select tag value'};
|
||||
let newTagParam = renderTagString(newTag);
|
||||
this.getSeriesByTagFunc().params.push(newTagParam);
|
||||
this.tags.push(newTag);
|
||||
this.targetChanged();
|
||||
this.fixTagSegments();
|
||||
}
|
||||
|
||||
removeTag(index) {
|
||||
this.getSeriesByTagFunc().params.splice(index, 1);
|
||||
this.tags.splice(index, 1);
|
||||
this.targetChanged();
|
||||
}
|
||||
|
||||
fixTagSegments() {
|
||||
// Adding tag with the same name as just removed works incorrectly if single segment is used (instead of array)
|
||||
this.addTagSegments = [this.uiSegmentSrv.newPlusButton()];
|
||||
}
|
||||
|
||||
showDelimiter(index) {
|
||||
return index !== this.tags.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function renderTagString(tag) {
|
||||
return tag.key + tag.operator + tag.value;
|
||||
}
|
||||
|
||||
function mapToDropdownOptions(results) {
|
||||
return _.map(results, (value) => {
|
||||
return {text: value, value: value};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,4 +210,113 @@ describe('GraphiteQueryCtrl', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when adding seriesByTag function', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = '';
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.addFunction(gfunc.getFuncDef('seriesByTag'));
|
||||
});
|
||||
|
||||
it('should update functions', function() {
|
||||
expect(ctx.ctrl.getSeriesByTagFuncIndex()).to.be(0);
|
||||
});
|
||||
|
||||
it('should update seriesByTagUsed flag', function() {
|
||||
expect(ctx.ctrl.seriesByTagUsed).to.be(true);
|
||||
});
|
||||
|
||||
it('should update target', function() {
|
||||
expect(ctx.ctrl.target.target).to.be('seriesByTag()');
|
||||
});
|
||||
|
||||
it('should call refresh', function() {
|
||||
expect(ctx.panelCtrl.refresh.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing seriesByTag function', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = "seriesByTag('tag1=value1', 'tag2!=~value2')";
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
});
|
||||
|
||||
it('should add tags', function() {
|
||||
const expected = [
|
||||
{key: 'tag1', operator: '=', value: 'value1'},
|
||||
{key: 'tag2', operator: '!=~', value: 'value2'}
|
||||
];
|
||||
expect(ctx.ctrl.tags).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should add plus button', function() {
|
||||
expect(ctx.ctrl.addTagSegments.length).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when tag added', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = "seriesByTag()";
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.addNewTag({value: 'tag1'});
|
||||
});
|
||||
|
||||
it('should update tags with default value', function() {
|
||||
const expected = [
|
||||
{key: 'tag1', operator: '=', value: 'select tag value'}
|
||||
];
|
||||
expect(ctx.ctrl.tags).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should update target', function() {
|
||||
const expected = "seriesByTag('tag1=select tag value')";
|
||||
expect(ctx.ctrl.target.target).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when tag changed', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = "seriesByTag('tag1=value1', 'tag2!=~value2')";
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.tagChanged({key: 'tag1', operator: '=', value: 'new_value'}, 0);
|
||||
});
|
||||
|
||||
it('should update tags', function() {
|
||||
const expected = [
|
||||
{key: 'tag1', operator: '=', value: 'new_value'},
|
||||
{key: 'tag2', operator: '!=~', value: 'value2'}
|
||||
];
|
||||
expect(ctx.ctrl.tags).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should update target', function() {
|
||||
const expected = "seriesByTag('tag1=new_value', 'tag2!=~value2')";
|
||||
expect(ctx.ctrl.target.target).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when tag removed', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = "seriesByTag('tag1=value1', 'tag2!=~value2')";
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
ctx.ctrl.removeTag(0);
|
||||
});
|
||||
|
||||
it('should update tags', function() {
|
||||
const expected = [
|
||||
{key: 'tag2', operator: '!=~', value: 'value2'}
|
||||
];
|
||||
expect(ctx.ctrl.tags).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should update target', function() {
|
||||
const expected = "seriesByTag('tag2!=~value2')";
|
||||
expect(ctx.ctrl.target.target).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user