diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index f75aa985b1a..cb416fa8f0c 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -141,6 +141,7 @@ export class PrometheusDatasource this.cache = new QueryCache({ getTargetSignature: this.getPrometheusTargetSignature.bind(this), overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow, + applyInterpolation: this.interpolateString.bind(this), }); // This needs to be here and cannot be static because of how annotations typing affects casting of data source diff --git a/packages/grafana-prometheus/src/querycache/QueryCache.test.ts b/packages/grafana-prometheus/src/querycache/QueryCache.test.ts index 8d6fd349a84..01cd25fd14f 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCache.test.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCache.test.ts @@ -6,7 +6,7 @@ import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@gra import { QueryEditorMode } from '../querybuilder/shared/types'; import { PromQuery } from '../types'; -import { CacheRequestInfo, QueryCache } from './QueryCache'; +import { CacheRequestInfo, findDatapointStep, QueryCache } from './QueryCache'; import { IncrementalStorageDataFrameScenarios, trimmedFirstPointInPromFrames } from './QueryCacheTestData'; // Will not interpolate vars! @@ -559,3 +559,14 @@ describe('QueryCache: Prometheus', function () { expect(cacheRequest.shouldCache).toBe(true); }); }); + +describe('findDataPointStep', () => { + it('should interpolate custom interval', () => { + const mockApplyInterpolation = jest.fn().mockImplementation(() => '1m'); + const req = mockPromRequest(); + req.targets[0].interval = '$interval'; + const respFrames = trimmedFirstPointInPromFrames as unknown as DataFrame[]; + findDatapointStep(req, respFrames, mockApplyInterpolation); + expect(mockApplyInterpolation).toBeCalledTimes(1); + }); +}); diff --git a/packages/grafana-prometheus/src/querycache/QueryCache.ts b/packages/grafana-prometheus/src/querycache/QueryCache.ts index 9b9c308e022..9b06746b7cb 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCache.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCache.ts @@ -10,6 +10,7 @@ import { isValidDuration, parseDuration, rangeUtil, + ScopedVars, Table, trimTable, } from '@grafana/data'; @@ -23,10 +24,9 @@ type TargetIdent = string; // query + template variables + interval + raw time range // used for full target cache busting -> full range re-query type TargetSig = string; - type TimestampMs = number; - type SupportedQueryTypes = PromQuery; +type ApplyInterpolation = (str: string, scopedVars?: ScopedVars) => string; // string matching requirements defined in durationutil.ts export const defaultPrometheusQueryOverlapWindow = '10m'; @@ -59,12 +59,14 @@ export const getFieldIdent = (field: Field) => `${field.type}|${field.name}|${JS export class QueryCache { private overlapWindowMs: number; private getTargetSignature: (request: DataQueryRequest, target: T) => string; + private applyInterpolation = (str: string, scopedVars?: ScopedVars) => str; cache = new Map(); constructor(options: { getTargetSignature: (request: DataQueryRequest, target: T) => string; overlapString: string; + applyInterpolation?: ApplyInterpolation; }) { const unverifiedOverlap = options.overlapString; if (isValidDuration(unverifiedOverlap)) { @@ -76,6 +78,9 @@ export class QueryCache { } this.getTargetSignature = options.getTargetSignature; + if (options.applyInterpolation) { + this.applyInterpolation = options.applyInterpolation; + } } // can be used to change full range request to partial, split into multiple requests @@ -221,7 +226,7 @@ export class QueryCache { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions let table: Table = frame.fields.map((field) => field.values) as Table; - const dataPointStep = findDatapointStep(request, respFrames); + const dataPointStep = findDatapointStep(request, respFrames, this.applyInterpolation); // query interval is greater than request.intervalMs, use query interval to make sure we've always got one datapoint outside the panel viewport let trimmed = trimTable(table, newFrom - dataPointStep, newTo); @@ -260,7 +265,11 @@ export class QueryCache { } } -function findDatapointStep(request: DataQueryRequest, respFrames: DataFrame[]): number { +export function findDatapointStep( + request: DataQueryRequest, + respFrames: DataFrame[], + applyInterpolation: ApplyInterpolation +): number { // Prometheus specific logic below if (request.targets[0].datasource?.type !== 'prometheus') { return 0; @@ -270,7 +279,7 @@ function findDatapointStep(request: DataQueryRequest, respFrames: Dat let dataPointStep = request.intervalMs; if (target?.interval) { - const minStepMs = rangeUtil.intervalToMs(target.interval); + const minStepMs = rangeUtil.intervalToMs(applyInterpolation(target.interval)); if (minStepMs > request.intervalMs) { dataPointStep = minStepMs; }