diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index f2a73ded7eb..cb01348fdcd 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -510,6 +510,48 @@ describe('graphiteDatasource', () => { expect(results[0]).toBe('target=' + encodeURIComponent('avg(web01.cpu)')); }); + it('should not recursively replace queries that reference themselves', () => { + const originalTargetMap = { + A: 'sumSeries(carbon.test.test-host.cpuUsage, #A)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'sumSeries(carbon.test.test-host.cpuUsage, #A)' }], + }, + originalTargetMap + ); + expect(results[0]).toBe( + 'target=' + + encodeURIComponent('sumSeries(carbon.test.test-host.cpuUsage, sumSeries(carbon.test.test-host.cpuUsage, #A))') + ); + }); + + it('should not recursively replace queries that reference themselves, but will replace nested references', () => { + const originalTargetMap = { + A: 'sumSeries(carbon.test.test-host.cpuUsage, #A, #B)', + B: 'add(carbon.test.test-host.cpuUsage, 1.5)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [ + { + target: 'sumSeries(carbon.test.test-host.cpuUsage, #A, #B)', + }, + { + target: 'add(carbon.test.test-host.cpuUsage, 1.5)', + }, + ], + }, + originalTargetMap + ); + expect(results[0]).toBe( + 'target=' + + encodeURIComponent( + 'sumSeries(carbon.test.test-host.cpuUsage, sumSeries(carbon.test.test-host.cpuUsage, #A, #B), add(carbon.test.test-host.cpuUsage, 1.5))' + ) + ); + }); + it('should fix wrong minute interval parameters', () => { const originalTargetMap = { A: "summarize(prod.25m.count, '25m', 'sum')", diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 3f924dce7b5..d5415d0eed5 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1008,8 +1008,7 @@ export class GraphiteDatasource const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; const cleanOptions = [], targets: Record = {}; - let target, targetValue, i; - const regex = /\#([A-Z])/g; + let target: GraphiteQuery, targetValue, i; const intervalFormatFixRegex = /'(\d+)m'/gi; let hasTargets = false; @@ -1034,7 +1033,14 @@ export class GraphiteDatasource targets[target.refId] = targetValue; } + const regex = /\#([A-Z])/g; + function nestedSeriesRegexReplacer(match: string, g1: string | number) { + // Handle the case where a query references itself to prevent infinite recursion + if (target.refId === g1) { + return targets[g1] || match; + } + // Recursively replace all nested series references return originalTargetMap[g1].replace(regex, nestedSeriesRegexReplacer) || match; }