[release-11.6.2] Graphite: Fix Graphite series interpolation (#104568)
* Graphite: Fix Graphite series interpolation (#104471)
* Fix graphite series interpolation
* Trigger build
(cherry picked from commit 3b036f0e78)
* Trigger build
This commit is contained in:
@@ -5,9 +5,9 @@ import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
|||||||
import {
|
import {
|
||||||
AbstractLabelMatcher,
|
AbstractLabelMatcher,
|
||||||
AbstractLabelOperator,
|
AbstractLabelOperator,
|
||||||
getFrameDisplayName,
|
|
||||||
dateTime,
|
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
|
dateTime,
|
||||||
|
getFrameDisplayName,
|
||||||
MetricFindValue,
|
MetricFindValue,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { BackendSrvRequest } from '@grafana/runtime';
|
import { BackendSrvRequest } from '@grafana/runtime';
|
||||||
@@ -373,62 +373,116 @@ describe('graphiteDatasource', () => {
|
|||||||
|
|
||||||
describe('building graphite params', () => {
|
describe('building graphite params', () => {
|
||||||
it('should return empty array if no targets', () => {
|
it('should return empty array if no targets', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = { A: '' };
|
||||||
targets: [{}],
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
});
|
{
|
||||||
|
targets: [{}],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results.length).toBe(0);
|
expect(results.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should uri escape targets', () => {
|
it('should uri escape targets', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }],
|
A: 'prod1.{test,test2}',
|
||||||
});
|
B: 'prod2.count',
|
||||||
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results).toContain('target=prod1.%7Btest%2Ctest2%7D');
|
expect(results).toContain('target=prod1.%7Btest%2Ctest2%7D');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should replace target placeholder', () => {
|
it('should replace target placeholder', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }],
|
A: 'series1',
|
||||||
});
|
B: 'series2',
|
||||||
|
C: 'asPercent(#A,#B)',
|
||||||
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results[2]).toBe('target=asPercent(series1%2Cseries2)');
|
expect(results[2]).toBe('target=asPercent(series1%2Cseries2)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should replace target placeholder for hidden series', () => {
|
it('should replace target placeholder for hidden series', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [
|
A: 'series1',
|
||||||
{ target: 'series1', hide: true },
|
B: 'sumSeries(#A)',
|
||||||
{ target: 'sumSeries(#A)', hide: true },
|
C: 'asPercent(#A,#B)',
|
||||||
{ target: 'asPercent(#A,#B)' },
|
};
|
||||||
],
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
});
|
{
|
||||||
|
targets: [
|
||||||
|
{ target: 'series1', hide: true },
|
||||||
|
{ target: 'sumSeries(#A)', hide: true },
|
||||||
|
{ target: 'asPercent(#A,#B)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results[0]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
|
expect(results[0]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should replace target placeholder when nesting query references', () => {
|
it('should replace target placeholder when nesting query references', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }],
|
A: 'series1',
|
||||||
});
|
B: 'sumSeries(#A)',
|
||||||
|
C: 'asPercent(#A,#B)',
|
||||||
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results[2]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
|
expect(results[2]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fix wrong minute interval parameters', () => {
|
it('should fix wrong minute interval parameters', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }],
|
A: "summarize(prod.25m.count, '25m', 'sum')",
|
||||||
});
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.25m.count, '25min', 'sum')"));
|
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.25m.count, '25min', 'sum')"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fix wrong month interval parameters', () => {
|
it('should fix wrong month interval parameters', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }],
|
A: "summarize(prod.5M.count, '5M', 'sum')",
|
||||||
});
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.5M.count, '5mon', 'sum')"));
|
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.5M.count, '5mon', 'sum')"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore empty targets', () => {
|
it('should ignore empty targets', () => {
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: 'series1' }, { target: '' }],
|
A: 'series1',
|
||||||
});
|
B: '',
|
||||||
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: 'series1' }, { target: '' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results.length).toBe(2);
|
expect(results.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,9 +496,15 @@ describe('graphiteDatasource', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const originalTargetMap = {
|
||||||
targets: [{ target: 'my.$metric.*' }],
|
A: 'my.$metric.*',
|
||||||
});
|
};
|
||||||
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
|
{
|
||||||
|
targets: [{ target: 'my.$metric.*' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
expect(results).toStrictEqual(['target=my.b.*', 'format=json']);
|
expect(results).toStrictEqual(['target=my.b.*', 'format=json']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -456,10 +516,13 @@ describe('graphiteDatasource', () => {
|
|||||||
current: { value: ['a', 'b'] },
|
current: { value: ['a', 'b'] },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
const originalTargetMap = { A: 'my.[[metric]].*' };
|
||||||
const results = ctx.ds.buildGraphiteParams({
|
const results = ctx.ds.buildGraphiteParams(
|
||||||
targets: [{ target: 'my.[[metric]].*' }],
|
{
|
||||||
});
|
targets: [{ target: 'my.[[metric]].*' }],
|
||||||
|
},
|
||||||
|
originalTargetMap
|
||||||
|
);
|
||||||
|
|
||||||
expect(results).toStrictEqual(['target=my.%7Ba%2Cb%7D.*', 'format=json']);
|
expect(results).toStrictEqual(['target=my.%7Ba%2Cb%7D.*', 'format=json']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { each, indexOf, isArray, isString, map as _map } from 'lodash';
|
import { map as _map, each, indexOf, isArray, isString } from 'lodash';
|
||||||
import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
|
import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
DataSourceWithQueryExportSupport,
|
DataSourceWithQueryExportSupport,
|
||||||
dateMath,
|
dateMath,
|
||||||
dateTime,
|
dateTime,
|
||||||
|
getSearchFilterScopedVar,
|
||||||
MetricFindValue,
|
MetricFindValue,
|
||||||
QueryResultMetaStat,
|
QueryResultMetaStat,
|
||||||
ScopedVars,
|
ScopedVars,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
getSearchFilterScopedVar,
|
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime';
|
import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime';
|
||||||
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
|
||||||
@@ -210,12 +210,19 @@ export class GraphiteDatasource
|
|||||||
return merge(...streams);
|
return merge(...streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use this object to map the original refID of the query to our sanitised one
|
// Use this object to map the sanitised refID to the original
|
||||||
const refIds: { [key: string]: string } = {};
|
const formattedRefIdsMap: { [key: string]: string } = {};
|
||||||
|
// Use this object to map the original refID to the original target
|
||||||
|
const originalTargetMap: { [key: string]: string } = {};
|
||||||
for (const target of options.targets) {
|
for (const target of options.targets) {
|
||||||
// Sanitise the refID otherwise the Graphite query will fail
|
// Sanitise the refID otherwise the Graphite query will fail
|
||||||
const formattedRefId = target.refId.replaceAll(' ', '_');
|
const formattedRefId = target.refId.replaceAll(' ', '_');
|
||||||
refIds[formattedRefId] = target.refId;
|
formattedRefIdsMap[formattedRefId] = target.refId;
|
||||||
|
// Track the original target to ensure if we need to interpolate a series, we interpolate using the original target
|
||||||
|
// rather than the target wrapped in aliasSub e.g.:
|
||||||
|
// Suppose a query has three targets: A: metric1 B: sumSeries(#A) and C: asPercent(#A, #B)
|
||||||
|
// We want the targets to be interpolated to: A: aliasSub(metric1, "(^.*$)", "\\1 A"), B: aliasSub(sumSeries(metric1), "(^.*$)", "\\1 B") and C: asPercent(metric1, sumSeries(metric1))
|
||||||
|
originalTargetMap[target.refId] = target.target || '';
|
||||||
// Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame.
|
// Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame.
|
||||||
const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`;
|
const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`;
|
||||||
target.target = updatedTarget;
|
target.target = updatedTarget;
|
||||||
@@ -231,7 +238,7 @@ export class GraphiteDatasource
|
|||||||
maxDataPoints: options.maxDataPoints,
|
maxDataPoints: options.maxDataPoints,
|
||||||
};
|
};
|
||||||
|
|
||||||
const params = this.buildGraphiteParams(graphOptions, options.scopedVars);
|
const params = this.buildGraphiteParams(graphOptions, originalTargetMap, options.scopedVars);
|
||||||
if (params.length === 0) {
|
if (params.length === 0) {
|
||||||
return of({ data: [] });
|
return of({ data: [] });
|
||||||
}
|
}
|
||||||
@@ -255,7 +262,9 @@ export class GraphiteDatasource
|
|||||||
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
|
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.doGraphiteRequest(httpOptions).pipe(map((result) => this.convertResponseToDataFrames(result, refIds)));
|
return this.doGraphiteRequest(httpOptions).pipe(
|
||||||
|
map((result) => this.convertResponseToDataFrames(result, formattedRefIdsMap))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTracingHeaders(
|
addTracingHeaders(
|
||||||
@@ -975,7 +984,7 @@ export class GraphiteDatasource
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildGraphiteParams(options: any, scopedVars?: ScopedVars): string[] {
|
buildGraphiteParams(options: any, originalTargetMap: { [key: string]: string }, scopedVars?: ScopedVars): string[] {
|
||||||
const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
|
||||||
const cleanOptions = [],
|
const cleanOptions = [],
|
||||||
targets: Record<string, string> = {};
|
targets: Record<string, string> = {};
|
||||||
@@ -1006,7 +1015,8 @@ export class GraphiteDatasource
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nestedSeriesRegexReplacer(match: string, g1: string | number) {
|
function nestedSeriesRegexReplacer(match: string, g1: string | number) {
|
||||||
return targets[g1] || match;
|
// Recursively replace all nested series references
|
||||||
|
return originalTargetMap[g1].replace(regex, nestedSeriesRegexReplacer) || match;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < options.targets.length; i++) {
|
for (i = 0; i < options.targets.length; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user