Prometheus: Remove timeSrv imports (#76118)

* Remove timeSrv imports from prometheus datasource

* Fix language_provider unit test

* Fix datasource unit tests

* Remove timeSrv imports from metric_find_query

* Remove timeSrv imports

* MetricsBrowser with timeRange
This commit is contained in:
ismail simsek 2023-10-30 15:44:28 +01:00 committed by GitHub
parent e5f92c010d
commit df3184a94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 211 additions and 161 deletions

View File

@ -713,7 +713,7 @@ abstract class LanguageProvider {
* Returns startTask that resolves with a task list when main syntax is loaded. * Returns startTask that resolves with a task list when main syntax is loaded.
* Task list consists of secondary promises that load more detailed language features. * Task list consists of secondary promises that load more detailed language features.
*/ */
abstract start: () => Promise<Array<Promise<any>>>; abstract start: (timeRange?: TimeRange) => Promise<Array<Promise<any>>>;
startTask?: Promise<any[]>; startTask?: Promise<any[]>;
} }

View File

@ -174,10 +174,11 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
refreshMetrics = async () => { refreshMetrics = async () => {
const { const {
range,
datasource: { languageProvider }, datasource: { languageProvider },
} = this.props; } = this.props;
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start()); this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start(range));
try { try {
const remainingTasks = await this.languageProviderInitializationPromise.promise; const remainingTasks = await this.languageProviderInitializationPromise.promise;
@ -327,6 +328,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
lastUsedLabels={lastUsedLabels || []} lastUsedLabels={lastUsedLabels || []}
storeLastUsedLabels={onLastUsedLabelsSave} storeLastUsedLabels={onLastUsedLabelsSave}
deleteLastUsedLabels={onLastUsedLabelsDelete} deleteLastUsedLabels={onLastUsedLabelsDelete}
timeRange={this.props.range}
/> />
</div> </div>
)} )}

View File

@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
import React, { ChangeEvent } from 'react'; import React, { ChangeEvent } from 'react';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2, TimeRange } from '@grafana/data';
import { import {
Button, Button,
HorizontalGroup, HorizontalGroup,
@ -31,6 +31,7 @@ export interface BrowserProps {
lastUsedLabels: string[]; lastUsedLabels: string[];
storeLastUsedLabels: (labels: string[]) => void; storeLastUsedLabels: (labels: string[]) => void;
deleteLastUsedLabels: () => void; deleteLastUsedLabels: () => void;
timeRange?: TimeRange;
} }
interface BrowserState { interface BrowserState {
@ -319,7 +320,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
const { languageProvider, lastUsedLabels } = this.props; const { languageProvider, lastUsedLabels } = this.props;
if (languageProvider) { if (languageProvider) {
const selectedLabels: string[] = lastUsedLabels; const selectedLabels: string[] = lastUsedLabels;
languageProvider.start().then(() => { languageProvider.start(this.props.timeRange).then(() => {
let rawLabels: string[] = languageProvider.getLabelKeys(); let rawLabels: string[] = languageProvider.getLabelKeys();
// Get metrics // Get metrics
this.fetchValues(METRIC_LABEL, EMPTY_SELECTOR); this.fetchValues(METRIC_LABEL, EMPTY_SELECTOR);

View File

@ -13,10 +13,11 @@ import {
Field, Field,
getFieldDisplayName, getFieldDisplayName,
LoadingState, LoadingState,
rangeUtil,
TimeRange,
toDataFrame, toDataFrame,
VariableHide, VariableHide,
} from '@grafana/data'; } from '@grafana/data';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
import { QueryOptions } from 'app/types'; import { QueryOptions } from 'app/types';
@ -49,23 +50,23 @@ const templateSrvStub = {
const fromSeconds = 1674500289215; const fromSeconds = 1674500289215;
const toSeconds = 1674500349215; const toSeconds = 1674500349215;
const timeSrvStubOld = { const mockTimeRangeOld: TimeRange = {
timeRange() { from: dateTime(1531468681),
return { to: dateTime(1531489712),
from: dateTime(1531468681), raw: {
to: dateTime(1531489712), from: '1531468681',
}; to: '1531489712',
}, },
} as TimeSrv; };
const timeSrvStub: TimeSrv = { const mockTimeRange: TimeRange = {
timeRange() { from: dateTime(fromSeconds),
return { to: dateTime(toSeconds),
from: dateTime(fromSeconds), raw: {
to: dateTime(toSeconds), from: fromSeconds.toString(),
}; to: toSeconds.toString(),
}, },
} as TimeSrv; };
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -87,7 +88,7 @@ describe('PrometheusDatasource', () => {
} as unknown as DataSourceInstanceSettings<PromOptions>; } as unknown as DataSourceInstanceSettings<PromOptions>;
beforeEach(() => { beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
}); });
// Some functions are required by the parent datasource class to provide functionality such as ad-hoc filters, which requires the definition of the getTagKeys, and getTagValues functions // Some functions are required by the parent datasource class to provide functionality such as ad-hoc filters, which requires the definition of the getTagKeys, and getTagValues functions
@ -142,7 +143,7 @@ describe('PrometheusDatasource', () => {
}, },
} as unknown as DataSourceInstanceSettings<PromOptions>; } as unknown as DataSourceInstanceSettings<PromOptions>;
const range = { from: time({ seconds: 63 }), to: time({ seconds: 183 }) }; const range = { from: time({ seconds: 63 }), to: time({ seconds: 183 }) };
const directDs = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const directDs = new PrometheusDatasource(instanceSettings, templateSrvStub);
await expect( await expect(
lastValueFrom(directDs.query(createDataRequest([{}, {}], { app: CoreApp.Dashboard }))) lastValueFrom(directDs.query(createDataRequest([{}, {}], { app: CoreApp.Dashboard })))
@ -191,7 +192,7 @@ describe('PrometheusDatasource', () => {
it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => { it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => {
const postSettings = cloneDeep(instanceSettings); const postSettings = cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST'; postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub, timeSrvStub); const promDs = new PrometheusDatasource(postSettings, templateSrvStub);
promDs.metadataRequest('/foo'); promDs.metadataRequest('/foo');
expect(fetchMock.mock.calls.length).toBe(1); expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('GET'); expect(fetchMock.mock.calls[0][0].method).toBe('GET');
@ -199,7 +200,7 @@ describe('PrometheusDatasource', () => {
it('should try to perform a POST request with the DS HTTP method set to POST and POST-friendly endpoint', () => { it('should try to perform a POST request with the DS HTTP method set to POST and POST-friendly endpoint', () => {
const postSettings = cloneDeep(instanceSettings); const postSettings = cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST'; postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub, timeSrvStub); const promDs = new PrometheusDatasource(postSettings, templateSrvStub);
promDs.metadataRequest('api/v1/series', { bar: 'baz baz', foo: 'foo' }); promDs.metadataRequest('api/v1/series', { bar: 'baz baz', foo: 'foo' });
expect(fetchMock.mock.calls.length).toBe(1); expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('POST'); expect(fetchMock.mock.calls[0][0].method).toBe('POST');
@ -222,8 +223,7 @@ describe('PrometheusDatasource', () => {
describe('with GET http method', () => { describe('with GET http method', () => {
const promDs = new PrometheusDatasource( const promDs = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'GET' } }, { ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'GET' } },
templateSrvStub, templateSrvStub
timeSrvStub
); );
it('added to metadata request', () => { it('added to metadata request', () => {
@ -257,8 +257,7 @@ describe('PrometheusDatasource', () => {
describe('with POST http method', () => { describe('with POST http method', () => {
const promDs = new PrometheusDatasource( const promDs = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'POST' } }, { ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'POST' } },
templateSrvStub, templateSrvStub
timeSrvStub
); );
it('added to metadata request with non-POST endpoint', () => { it('added to metadata request with non-POST endpoint', () => {
@ -307,7 +306,12 @@ describe('PrometheusDatasource', () => {
const target: PromQuery = { expr: DEFAULT_QUERY_EXPRESSION, refId: 'A' }; const target: PromQuery = { expr: DEFAULT_QUERY_EXPRESSION, refId: 'A' };
it('should not modify expression with no filters', () => { it('should not modify expression with no filters', () => {
const result = ds.createQuery(target, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 0); const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({ expr: DEFAULT_QUERY_EXPRESSION }); expect(result).toMatchObject({ expr: DEFAULT_QUERY_EXPRESSION });
}); });
@ -324,7 +328,12 @@ describe('PrometheusDatasource', () => {
value: 'v2', value: 'v2',
}, },
]; ];
const result = ds.createQuery(target, { interval: '15s', filters } as DataQueryRequest<PromQuery>, 0, 0); const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange(), filters } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({ expr: 'metric{job="foo", k1="v1", k2!="v2"} - metric{k1="v1", k2!="v2"}' }); expect(result).toMatchObject({ expr: 'metric{job="foo", k1="v1", k2!="v2"} - metric{k1="v1", k2!="v2"}' });
}); });
@ -342,7 +351,12 @@ describe('PrometheusDatasource', () => {
}, },
]; ];
const result = ds.createQuery(target, { interval: '15s', filters } as DataQueryRequest<PromQuery>, 0, 0); const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange(), filters } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({ expect(result).toMatchObject({
expr: `metric{job="foo", k1=~"v.*", k2=~"v\\\\'.*"} - metric{k1=~"v.*", k2=~"v\\\\'.*"}`, expr: `metric{job="foo", k1=~"v.*", k2=~"v\\\\'.*"} - metric{k1=~"v.*", k2=~"v\\\\'.*"}`,
}); });
@ -445,10 +459,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings, ...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Low }, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Low },
}, },
templateSrvStub as unknown as TemplateSrv, templateSrvStub as unknown as TemplateSrv
timeSrvStub as unknown as TimeSrv
); );
const quantizedRange = dataSource.getAdjustedInterval(); const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
// For "1 minute" the window contains all the minutes, so a query from 1:11:09 - 1:12:09 becomes 1:11 - 1:13 // For "1 minute" the window contains all the minutes, so a query from 1:11:09 - 1:12:09 becomes 1:11 - 1:13
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(120); expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(120);
}); });
@ -459,10 +472,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings, ...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Medium }, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Medium },
}, },
templateSrvStub as unknown as TemplateSrv, templateSrvStub as unknown as TemplateSrv
timeSrvStub as unknown as TimeSrv
); );
const quantizedRange = dataSource.getAdjustedInterval(); const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(600); expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(600);
}); });
@ -472,10 +484,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings, ...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.High }, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.High },
}, },
templateSrvStub as unknown as TemplateSrv, templateSrvStub as unknown as TemplateSrv
timeSrvStub as unknown as TimeSrv
); );
const quantizedRange = dataSource.getAdjustedInterval(); const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(3600); expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(3600);
}); });
@ -485,10 +496,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings, ...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None }, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None },
}, },
templateSrvStub as unknown as TemplateSrv, templateSrvStub as unknown as TemplateSrv
timeSrvStub as unknown as TimeSrv
); );
const quantizedRange = dataSource.getAdjustedInterval(); const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe( expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(
(toSeconds - fromSeconds) / 1000 (toSeconds - fromSeconds) / 1000
); );
@ -840,11 +850,10 @@ describe('PrometheusDatasource', () => {
beforeEach(() => { beforeEach(() => {
const prometheusDatasource = new PrometheusDatasource( const prometheusDatasource = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None } }, { ...instanceSettings, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None } },
templateSrvStub, templateSrvStub
timeSrvStubOld
); );
const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))'; const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
prometheusDatasource.metricFindQuery(query); prometheusDatasource.metricFindQuery(query, { range: mockTimeRangeOld });
}); });
it('should call templateSrv.replace with scopedVars', () => { it('should call templateSrv.replace with scopedVars', () => {
@ -888,7 +897,7 @@ describe('PrometheusDatasource2', () => {
let ds: PrometheusDatasource; let ds: PrometheusDatasource;
beforeEach(() => { beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
}); });
describe('When querying prometheus with one target using query editor target spec', () => { describe('When querying prometheus with one target using query editor target spec', () => {
@ -1510,7 +1519,7 @@ describe('PrometheusDatasource2', () => {
it('should be determined by the 11000 data points limit when too small', async () => { it('should be determined by the 11000 data points limit when too small', async () => {
const query = { const query = {
// 1 week range // 1 week range
range: { from: time({}), to: time({ hours: 7 * 24 }) }, range: { from: time({ minutes: 1 }), to: time({ hours: 7 * 24, minutes: 1 }) },
targets: [ targets: [
{ {
expr: 'test', expr: 'test',
@ -1521,9 +1530,9 @@ describe('PrometheusDatasource2', () => {
} as DataQueryRequest<PromQuery>; } as DataQueryRequest<PromQuery>;
let end = 7 * 24 * 60 * 60; let end = 7 * 24 * 60 * 60;
end -= end % 55; end -= end % 55;
const start = 0; const start = 60;
const step = 55; const step = 55;
const adjusted = alignRange(start, end, step, timeSrvStub.timeRange().to.utcOffset() * 60); const adjusted = alignRange(start, end, step, query.range.to.utcOffset() * 60);
const urlExpected = const urlExpected =
'proxied/api/v1/query_range?query=test' + 'proxied/api/v1/query_range?query=test' +
'&start=' + '&start=' +
@ -1760,7 +1769,7 @@ describe('PrometheusDatasource2', () => {
it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => { it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
const query = { const query = {
// 1 week range // 1 week range
range: { from: time({}), to: time({ hours: 7 * 24 }) }, range: { from: time({ minutes: 1 }), to: time({ hours: 7 * 24, minutes: 1 }) },
targets: [ targets: [
{ {
expr: 'rate(test[$__interval])', expr: 'rate(test[$__interval])',
@ -1775,9 +1784,9 @@ describe('PrometheusDatasource2', () => {
}; };
let end = 7 * 24 * 60 * 60; let end = 7 * 24 * 60 * 60;
end -= end % 55; end -= end % 55;
const start = 0; const start = 60;
const step = 55; const step = 55;
const adjusted = alignRange(start, end, step, timeSrvStub.timeRange().to.utcOffset() * 60); const adjusted = alignRange(start, end, step, query.range.to.utcOffset() * 60);
const urlExpected = const urlExpected =
'proxied/api/v1/query_range?query=' + 'proxied/api/v1/query_range?query=' +
encodeURIComponent('rate(test[$__interval])') + encodeURIComponent('rate(test[$__interval])') +
@ -1872,40 +1881,70 @@ describe('PrometheusDatasource2', () => {
}); });
it('should be 4 times the scrape interval if interval + scrape interval is lower', () => { it('should be 4 times the scrape interval if interval + scrape interval is lower', () => {
ds.createQuery(target, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300); ds.createQuery(target, { interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>, 0, 300);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s'); expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
}); });
it('should be interval + scrape interval if 4 times the scrape interval is lower', () => { it('should be interval + scrape interval if 4 times the scrape interval is lower', () => {
ds.createQuery(target, { interval: '5m' } as DataQueryRequest<PromQuery>, 0, 10080); ds.createQuery(target, { interval: '5m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>, 0, 10080);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('315s'); expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('315s');
}); });
it('should fall back to a scrape interval of 15s if min step is set to 0, resulting in 4*15s = 60s', () => { it('should fall back to a scrape interval of 15s if min step is set to 0, resulting in 4*15s = 60s', () => {
ds.createQuery({ ...target, interval: '' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300); ds.createQuery(
{ ...target, interval: '' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s'); expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
}); });
it('should be 4 times the scrape interval if min step set to 1m and interval is 15s', () => { it('should be 4 times the scrape interval if min step set to 1m and interval is 15s', () => {
// For a 5m graph, $__interval is 15s // For a 5m graph, $__interval is 15s
ds.createQuery({ ...target, interval: '1m' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300); ds.createQuery(
{ ...target, interval: '1m' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('240s'); expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('240s');
}); });
it('should be interval + scrape interval if min step set to 1m and interval is 5m', () => { it('should be interval + scrape interval if min step set to 1m and interval is 5m', () => {
// For a 7d graph, $__interval is 5m // For a 7d graph, $__interval is 5m
ds.createQuery({ ...target, interval: '1m' }, { interval: '5m' } as DataQueryRequest<PromQuery>, 0, 10080); ds.createQuery(
{ ...target, interval: '1m' },
{ interval: '5m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
10080
);
expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('360s'); expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('360s');
}); });
it('should be interval + scrape interval if resolution is set to 1/2 and interval is 10m', () => { it('should be interval + scrape interval if resolution is set to 1/2 and interval is 10m', () => {
// For a 7d graph, $__interval is 10m // For a 7d graph, $__interval is 10m
ds.createQuery({ ...target, intervalFactor: 2 }, { interval: '10m' } as DataQueryRequest<PromQuery>, 0, 10080); ds.createQuery(
{ ...target, intervalFactor: 2 },
{ interval: '10m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
10080
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('1215s'); expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('1215s');
}); });
it('should be 4 times the scrape interval if resolution is set to 1/2 and interval is 15s', () => { it('should be 4 times the scrape interval if resolution is set to 1/2 and interval is 15s', () => {
// For a 5m graph, $__interval is 15s // For a 5m graph, $__interval is 15s
ds.createQuery({ ...target, intervalFactor: 2 }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300); ds.createQuery(
{ ...target, intervalFactor: 2 },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s'); expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
}); });
it('should interpolate min step if set', () => { it('should interpolate min step if set', () => {
replaceMock.mockImplementation((_: string) => '15s'); replaceMock.mockImplementation((_: string) => '15s');
ds.createQuery({ ...target, interval: '$int' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300); ds.createQuery(
{ ...target, interval: '$int' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls).toHaveLength(3); expect(replaceMock.mock.calls).toHaveLength(3);
replaceMock.mockImplementation((str) => str); replaceMock.mockImplementation((str) => str);
}); });
@ -1952,7 +1991,7 @@ describe('PrometheusDatasource for POST', () => {
let ds: PrometheusDatasource; let ds: PrometheusDatasource;
beforeEach(() => { beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
}); });
describe('When querying prometheus with one target using query editor target spec', () => { describe('When querying prometheus with one target using query editor target spec', () => {
@ -2021,11 +2060,7 @@ describe('PrometheusDatasource for POST', () => {
let ds: PrometheusDatasource; let ds: PrometheusDatasource;
beforeEach(() => { beforeEach(() => {
ds = new PrometheusDatasource( ds = new PrometheusDatasource(instanceSettings, templateSrvStub as unknown as TemplateSrv);
instanceSettings,
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
);
}); });
it('with proxy access tracing headers should be added', () => { it('with proxy access tracing headers should be added', () => {
@ -2043,11 +2078,7 @@ describe('PrometheusDatasource for POST', () => {
jsonData: { httpMethod: 'POST' }, jsonData: { httpMethod: 'POST' },
} as unknown as DataSourceInstanceSettings<PromOptions>; } as unknown as DataSourceInstanceSettings<PromOptions>;
const mockDs = new PrometheusDatasource( const mockDs = new PrometheusDatasource({ ...instanceSettings, url: 'http://127.0.0.1:8000' }, templateSrvStub);
{ ...instanceSettings, url: 'http://127.0.0.1:8000' },
templateSrvStub,
timeSrvStub
);
mockDs._addTracingHeaders(httpOptions, options); mockDs._addTracingHeaders(httpOptions, options);
expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined); expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
expect(httpOptions.headers['X-Panel-Id']).toBe(undefined); expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
@ -2083,10 +2114,11 @@ function getPrepareTargetsContext({
interval: '1s', interval: '1s',
panelId, panelId,
app, app,
range: getMockTimeRange(),
...queryOptions, ...queryOptions,
} as unknown as DataQueryRequest<PromQuery>; } as unknown as DataQueryRequest<PromQuery>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
if (languageProvider) { if (languageProvider) {
ds.languageProvider = languageProvider; ds.languageProvider = languageProvider;
} }
@ -2392,7 +2424,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines' }; const query: PromQuery = { refId: 'A', expr: 'go_goroutines' };
const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER' }; const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>; const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action); const result = ds.modifyQuery(query, action);
@ -2406,7 +2438,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' }; const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' };
const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER' }; const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>; const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action); const result = ds.modifyQuery(query, action);
@ -2422,7 +2454,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines' }; const query: PromQuery = { refId: 'A', expr: 'go_goroutines' };
const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER_OUT' }; const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER_OUT' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>; const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action); const result = ds.modifyQuery(query, action);
@ -2436,7 +2468,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' }; const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' };
const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER_OUT' }; const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER_OUT' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>; const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub); const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action); const result = ds.modifyQuery(query, action);
@ -2559,3 +2591,10 @@ function createEmptyAnnotationResponse() {
return { ...response }; return { ...response };
} }
function getMockTimeRange(range = '6h'): TimeRange {
return rangeUtil.convertRawToRange({
from: `now-${range}`,
to: 'now',
});
}

View File

@ -19,6 +19,8 @@ import {
DataSourceWithQueryExportSupport, DataSourceWithQueryExportSupport,
DataSourceWithQueryImportSupport, DataSourceWithQueryImportSupport,
dateTime, dateTime,
getDefaultTimeRange,
LegacyMetricFindQueryOptions,
LoadingState, LoadingState,
MetricFindValue, MetricFindValue,
QueryFixAction, QueryFixAction,
@ -38,7 +40,6 @@ import {
toDataQueryResponse, toDataQueryResponse,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { safeStringifyValue } from 'app/core/utils/explore'; import { safeStringifyValue } from 'app/core/utils/explore';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { addLabelToQuery } from './add_label_to_query'; import { addLabelToQuery } from './add_label_to_query';
@ -109,7 +110,6 @@ export class PrometheusDatasource
constructor( constructor(
instanceSettings: DataSourceInstanceSettings<PromOptions>, instanceSettings: DataSourceInstanceSettings<PromOptions>,
private readonly templateSrv: TemplateSrv = getTemplateSrv(), private readonly templateSrv: TemplateSrv = getTemplateSrv(),
private readonly timeSrv: TimeSrv = getTimeSrv(),
languageProvider?: PrometheusLanguageProvider languageProvider?: PrometheusLanguageProvider
) { ) {
super(instanceSettings); super(instanceSettings);
@ -136,7 +136,7 @@ export class PrometheusDatasource
this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion; this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion;
this.defaultEditor = instanceSettings.jsonData.defaultEditor; this.defaultEditor = instanceSettings.jsonData.defaultEditor;
this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false; this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false;
this.variables = new PrometheusVariableSupport(this, this.templateSrv, this.timeSrv); this.variables = new PrometheusVariableSupport(this, this.templateSrv);
this.exemplarsAvailable = true; this.exemplarsAvailable = true;
this.cacheLevel = instanceSettings.jsonData.cacheLevel ?? PrometheusCacheLevel.Low; this.cacheLevel = instanceSettings.jsonData.cacheLevel ?? PrometheusCacheLevel.Low;
@ -448,7 +448,7 @@ export class PrometheusDatasource
exemplar: this.shouldRunExemplarQuery(target, request), exemplar: this.shouldRunExemplarQuery(target, request),
requestId: request.panelId + target.refId, requestId: request.panelId + target.refId,
// We need to pass utcOffsetSec to backend to calculate aligned range // We need to pass utcOffsetSec to backend to calculate aligned range
utcOffsetSec: this.timeSrv.timeRange().to.utcOffset() * 60, utcOffsetSec: request.range.to.utcOffset() * 60,
}; };
if (target.instant && target.range) { if (target.instant && target.range) {
// We have query type "Both" selected // We have query type "Both" selected
@ -673,7 +673,7 @@ export class PrometheusDatasource
// Align query interval with step to allow query caching and to ensure // Align query interval with step to allow query caching and to ensure
// that about-same-time query results look the same. // that about-same-time query results look the same.
const adjusted = alignRange(start, end, query.step, this.timeSrv.timeRange().to.utcOffset() * 60); const adjusted = alignRange(start, end, query.step, options.range.to.utcOffset() * 60);
query.start = adjusted.start; query.start = adjusted.start;
query.end = adjusted.end; query.end = adjusted.end;
this._addTracingHeaders(query, options); this._addTracingHeaders(query, options);
@ -789,7 +789,7 @@ export class PrometheusDatasource
return error; return error;
}; };
metricFindQuery(query: string) { metricFindQuery(query: string, options?: LegacyMetricFindQueryOptions) {
if (!query) { if (!query) {
return Promise.resolve([]); return Promise.resolve([]);
} }
@ -797,14 +797,14 @@ export class PrometheusDatasource
const scopedVars = { const scopedVars = {
__interval: { text: this.interval, value: this.interval }, __interval: { text: this.interval, value: this.interval },
__interval_ms: { text: rangeUtil.intervalToMs(this.interval), value: rangeUtil.intervalToMs(this.interval) }, __interval_ms: { text: rangeUtil.intervalToMs(this.interval), value: rangeUtil.intervalToMs(this.interval) },
...this.getRangeScopedVars(this.timeSrv.timeRange()), ...this.getRangeScopedVars(options?.range ?? getDefaultTimeRange()),
}; };
const interpolated = this.templateSrv.replace(query, scopedVars, this.interpolateQueryExpr); const interpolated = this.templateSrv.replace(query, scopedVars, this.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this, interpolated); const metricFindQuery = new PrometheusMetricFindQuery(this, interpolated);
return metricFindQuery.process(); return metricFindQuery.process(options?.range ?? getDefaultTimeRange());
} }
getRangeScopedVars(range: TimeRange = this.timeSrv.timeRange()) { getRangeScopedVars(range: TimeRange) {
const msRange = range.to.diff(range.from); const msRange = range.to.diff(range.from);
const sRange = Math.round(msRange / 1000); const sRange = Math.round(msRange / 1000);
return { return {
@ -963,7 +963,7 @@ export class PrometheusDatasource
// and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
async getTagKeys(options: DataSourceGetTagKeysOptions): Promise<MetricFindValue[]> { async getTagKeys(options: DataSourceGetTagKeysOptions): Promise<MetricFindValue[]> {
if (!options || options.filters.length === 0) { if (!options || options.filters.length === 0) {
await this.languageProvider.fetchLabels(); await this.languageProvider.fetchLabels(options.timeRange);
return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k })); return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k }));
} }
@ -1005,7 +1005,7 @@ export class PrometheusDatasource
})); }));
} }
const params = this.getTimeRangeParams(); const params = this.getTimeRangeParams(options.timeRange ?? getDefaultTimeRange());
const result = await this.metadataRequest(`/api/v1/label/${options.key}/values`, params); const result = await this.metadataRequest(`/api/v1/label/${options.key}/values`, params);
return result?.data?.data?.map((value: any) => ({ text: value })) ?? []; return result?.data?.data?.map((value: any) => ({ text: value })) ?? [];
} }
@ -1123,9 +1123,8 @@ export class PrometheusDatasource
/** /**
* Returns the adjusted "snapped" interval parameters * Returns the adjusted "snapped" interval parameters
*/ */
getAdjustedInterval(): { start: string; end: string } { getAdjustedInterval(timeRange: TimeRange): { start: string; end: string } {
const range = this.timeSrv.timeRange(); return getRangeSnapInterval(this.cacheLevel, timeRange);
return getRangeSnapInterval(this.cacheLevel, range);
} }
/** /**
@ -1133,16 +1132,15 @@ export class PrometheusDatasource
* and then a little extra padding to round up/down to the nearest nth minute, * and then a little extra padding to round up/down to the nearest nth minute,
* defined by the result of the getCacheDurationInMinutes. * defined by the result of the getCacheDurationInMinutes.
* *
* For longer cache durations, and shorter query durations, the window we're calculating might be much bigger then the user's current window, * For longer cache durations, and shorter query durations,
* resulting in us returning labels/values that might not be applicable for the given window, this is a necessary trade off if we want to cache larger durations * the window we're calculating might be much bigger then the user's current window,
* * resulting in us returning labels/values that might not be applicable for the given window,
* this is a necessary trade-off if we want to cache larger durations
*/ */
getTimeRangeParams(timeRange: TimeRange): { start: string; end: string } {
getTimeRangeParams(): { start: string; end: string } {
const range = this.timeSrv.timeRange();
return { return {
start: getPrometheusTime(range.from, false).toString(), start: getPrometheusTime(timeRange.from, false).toString(),
end: getPrometheusTime(range.to, true).toString(), end: getPrometheusTime(timeRange.to, true).toString(),
}; };
} }

View File

@ -17,7 +17,21 @@ const fromPrometheusTime = getPrometheusTime(dateTime(now - timeRangeDurationSec
const toPrometheusTimeString = toPrometheusTime.toString(10); const toPrometheusTimeString = toPrometheusTime.toString(10);
const fromPrometheusTimeString = fromPrometheusTime.toString(10); const fromPrometheusTimeString = fromPrometheusTime.toString(10);
const getTimeRangeParams = (override?: Partial<{ start: string; end: string }>): { start: string; end: string } => ({ const getMockTimeRange = (): TimeRange => {
return {
to: dateTime(now).utc(),
from: dateTime(now).subtract(timeRangeDurationSeconds, 'second').utc(),
raw: {
from: fromPrometheusTimeString,
to: toPrometheusTimeString,
},
};
};
const getTimeRangeParams = (
timRange: TimeRange,
override?: Partial<{ start: string; end: string }>
): { start: string; end: string } => ({
start: fromPrometheusTimeString, start: fromPrometheusTimeString,
end: toPrometheusTimeString, end: toPrometheusTimeString,
...override, ...override,
@ -125,7 +139,8 @@ describe('Language completion provider', () => {
it('should call series endpoint', () => { it('should call series endpoint', () => {
const languageProvider = new LanguageProvider({ const languageProvider = new LanguageProvider({
...defaultDatasource, ...defaultDatasource,
getAdjustedInterval: () => getRangeSnapInterval(PrometheusCacheLevel.None, getMockQuantizedTimeRangeParams()), getAdjustedInterval: (timeRange: TimeRange) =>
getRangeSnapInterval(PrometheusCacheLevel.None, getMockQuantizedTimeRangeParams()),
} as PrometheusDatasource); } as PrometheusDatasource);
const getSeriesLabels = languageProvider.getSeriesLabels; const getSeriesLabels = languageProvider.getSeriesLabels;
const requestSpy = jest.spyOn(languageProvider, 'request'); const requestSpy = jest.spyOn(languageProvider, 'request');
@ -152,7 +167,8 @@ describe('Language completion provider', () => {
...defaultDatasource, ...defaultDatasource,
hasLabelsMatchAPISupport: () => true, hasLabelsMatchAPISupport: () => true,
cacheLevel: PrometheusCacheLevel.Low, cacheLevel: PrometheusCacheLevel.Low,
getAdjustedInterval: () => getRangeSnapInterval(PrometheusCacheLevel.Low, getMockQuantizedTimeRangeParams()), getAdjustedInterval: (timeRange: TimeRange) =>
getRangeSnapInterval(PrometheusCacheLevel.Low, getMockQuantizedTimeRangeParams()),
getCacheDurationInMinutes: () => timeSnapMinutes, getCacheDurationInMinutes: () => timeSnapMinutes,
} as PrometheusDatasource); } as PrometheusDatasource);
const getSeriesLabels = languageProvider.getSeriesLabels; const getSeriesLabels = languageProvider.getSeriesLabels;
@ -251,16 +267,21 @@ describe('Language completion provider', () => {
}); });
describe('fetchSeries', () => { describe('fetchSeries', () => {
it('should use match[] parameter', () => { it('should use match[] parameter', async () => {
const languageProvider = new LanguageProvider(defaultDatasource); const languageProvider = new LanguageProvider(defaultDatasource);
const fetchSeries = languageProvider.fetchSeries; const timeRange = getMockTimeRange();
await languageProvider.start(timeRange);
const requestSpy = jest.spyOn(languageProvider, 'request'); const requestSpy = jest.spyOn(languageProvider, 'request');
fetchSeries('{job="grafana"}'); await languageProvider.fetchSeries('{job="grafana"}');
expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalled();
expect(requestSpy).toHaveBeenCalledWith( expect(requestSpy).toHaveBeenCalledWith(
'/api/v1/series', '/api/v1/series',
{}, {},
{ end: toPrometheusTimeString, 'match[]': '{job="grafana"}', start: fromPrometheusTimeString }, {
end: toPrometheusTimeString,
'match[]': '{job="grafana"}',
start: fromPrometheusTimeString,
},
undefined undefined
); );
}); });

View File

@ -7,8 +7,10 @@ import {
AbstractLabelOperator, AbstractLabelOperator,
AbstractQuery, AbstractQuery,
dateTime, dateTime,
getDefaultTimeRange,
HistoryItem, HistoryItem,
LanguageProvider, LanguageProvider,
TimeRange,
} from '@grafana/data'; } from '@grafana/data';
import { BackendSrvRequest } from '@grafana/runtime'; import { BackendSrvRequest } from '@grafana/runtime';
import { CompletionItem, CompletionItemGroup, SearchFunctionType, TypeaheadInput, TypeaheadOutput } from '@grafana/ui'; import { CompletionItem, CompletionItemGroup, SearchFunctionType, TypeaheadInput, TypeaheadOutput } from '@grafana/ui';
@ -107,7 +109,7 @@ interface AutocompleteContext {
const secondsInDay = 86400; const secondsInDay = 86400;
export default class PromQlLanguageProvider extends LanguageProvider { export default class PromQlLanguageProvider extends LanguageProvider {
histogramMetrics: string[]; histogramMetrics: string[];
timeRange?: { start: number; end: number }; timeRange: TimeRange;
metrics: string[]; metrics: string[];
metricsMetadata?: PromMetricsMetadata; metricsMetadata?: PromMetricsMetadata;
declare startTask: Promise<any>; declare startTask: Promise<any>;
@ -120,7 +122,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
this.datasource = datasource; this.datasource = datasource;
this.histogramMetrics = []; this.histogramMetrics = [];
this.timeRange = { start: 0, end: 0 }; this.timeRange = getDefaultTimeRange();
this.metrics = []; this.metrics = [];
Object.assign(this, initialValues); Object.assign(this, initialValues);
@ -155,7 +157,9 @@ export default class PromQlLanguageProvider extends LanguageProvider {
return defaultValue; return defaultValue;
}; };
start = async (): Promise<any[]> => { start = async (timeRange?: TimeRange): Promise<any[]> => {
this.timeRange = timeRange ?? getDefaultTimeRange();
if (this.datasource.lookupsDisabled) { if (this.datasource.lookupsDisabled) {
return []; return [];
} }
@ -504,7 +508,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
* @param key * @param key
*/ */
fetchLabelValues = async (key: string): Promise<string[]> => { fetchLabelValues = async (key: string): Promise<string[]> => {
const params = this.datasource.getAdjustedInterval(); const params = this.datasource.getAdjustedInterval(this.timeRange);
const interpolatedName = this.datasource.interpolateString(key); const interpolatedName = this.datasource.interpolateString(key);
const url = `/api/v1/label/${interpolatedName}/values`; const url = `/api/v1/label/${interpolatedName}/values`;
const value = await this.request(url, [], params, this.getDefaultCacheHeaders()); const value = await this.request(url, [], params, this.getDefaultCacheHeaders());
@ -518,9 +522,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
/** /**
* Fetches all label keys * Fetches all label keys
*/ */
async fetchLabels(): Promise<string[]> { async fetchLabels(timeRange?: TimeRange): Promise<string[]> {
if (timeRange) {
this.timeRange = timeRange;
}
const url = '/api/v1/labels'; const url = '/api/v1/labels';
const params = this.datasource.getAdjustedInterval(); const params = this.datasource.getAdjustedInterval(this.timeRange);
this.labelFetchTs = Date.now().valueOf(); this.labelFetchTs = Date.now().valueOf();
const res = await this.request(url, [], params, this.getDefaultCacheHeaders()); const res = await this.request(url, [], params, this.getDefaultCacheHeaders());
@ -554,7 +561,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
fetchSeriesValuesWithMatch = async (name: string, match?: string): Promise<string[]> => { fetchSeriesValuesWithMatch = async (name: string, match?: string): Promise<string[]> => {
const interpolatedName = name ? this.datasource.interpolateString(name) : null; const interpolatedName = name ? this.datasource.interpolateString(name) : null;
const interpolatedMatch = match ? this.datasource.interpolateString(match) : null; const interpolatedMatch = match ? this.datasource.interpolateString(match) : null;
const range = this.datasource.getAdjustedInterval(); const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = { const urlParams = {
...range, ...range,
...(interpolatedMatch && { 'match[]': interpolatedMatch }), ...(interpolatedMatch && { 'match[]': interpolatedMatch }),
@ -603,7 +610,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/ */
fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => { fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
const interpolatedName = this.datasource.interpolateString(name); const interpolatedName = this.datasource.interpolateString(name);
const range = this.datasource.getAdjustedInterval(); const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = { const urlParams = {
...range, ...range,
'match[]': interpolatedName, 'match[]': interpolatedName,
@ -623,7 +630,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/ */
fetchSeriesLabelsMatch = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => { fetchSeriesLabelsMatch = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
const interpolatedName = this.datasource.interpolateString(name); const interpolatedName = this.datasource.interpolateString(name);
const range = this.datasource.getAdjustedInterval(); const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = { const urlParams = {
...range, ...range,
'match[]': interpolatedName, 'match[]': interpolatedName,
@ -641,7 +648,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/ */
fetchSeries = async (match: string): Promise<Array<Record<string, string>>> => { fetchSeries = async (match: string): Promise<Array<Record<string, string>>> => {
const url = '/api/v1/series'; const url = '/api/v1/series';
const range = this.datasource.getTimeRangeParams(); const range = this.datasource.getTimeRangeParams(this.timeRange);
const params = { ...range, 'match[]': match }; const params = { ...range, 'match[]': match };
return await this.request(url, {}, params, this.getDefaultCacheHeaders()); return await this.request(url, {}, params, this.getDefaultCacheHeaders());
}; };

View File

@ -1,7 +1,7 @@
import 'whatwg-fetch'; // fetch polyfill needed backendSrv import 'whatwg-fetch'; // fetch polyfill needed backendSrv
import { of } from 'rxjs'; import { of } from 'rxjs';
import { DataSourceInstanceSettings, toUtc } from '@grafana/data'; import { DataSourceInstanceSettings, TimeRange, toUtc } from '@grafana/data';
import { FetchResponse } from '@grafana/runtime'; import { FetchResponse } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
@ -26,24 +26,16 @@ const instanceSettings = {
password: 'mupp', password: 'mupp',
jsonData: { httpMethod: 'GET' }, jsonData: { httpMethod: 'GET' },
} as Partial<DataSourceInstanceSettings<PromOptions>> as DataSourceInstanceSettings<PromOptions>; } as Partial<DataSourceInstanceSettings<PromOptions>> as DataSourceInstanceSettings<PromOptions>;
const raw = {
const raw: TimeRange = {
from: toUtc('2018-04-25 10:00'), from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'), to: toUtc('2018-04-25 11:00'),
raw: {
from: '2018-04-25 10:00',
to: '2018-04-25 11:00',
},
}; };
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
__esModule: true,
getTimeSrv: jest.fn().mockReturnValue({
timeRange() {
return {
from: raw.from,
to: raw.to,
raw: raw,
};
},
}),
}));
const templateSrvStub = { const templateSrvStub = {
getAdhocFilters: jest.fn().mockImplementation(() => []), getAdhocFilters: jest.fn().mockImplementation(() => []),
replace: jest.fn().mockImplementation((a: string) => a), replace: jest.fn().mockImplementation((a: string) => a),
@ -80,7 +72,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['name1', 'name2', 'name3'], data: ['name1', 'name2', 'name3'],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -102,7 +94,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['value1', 'value2', 'value3'], data: ['value1', 'value2', 'value3'],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -126,7 +118,7 @@ describe('PrometheusMetricFindQuery', () => {
], ],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -152,7 +144,7 @@ describe('PrometheusMetricFindQuery', () => {
], ],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -176,7 +168,7 @@ describe('PrometheusMetricFindQuery', () => {
], ],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(2); expect(results).toHaveLength(2);
expect(results[0].text).toBe('value1'); expect(results[0].text).toBe('value1');
@ -201,7 +193,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['metric1', 'metric2', 'metric3', 'nomatch'], data: ['metric1', 'metric2', 'metric3', 'nomatch'],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -228,7 +220,7 @@ describe('PrometheusMetricFindQuery', () => {
}, },
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(1); expect(results).toHaveLength(1);
expect(results[0].text).toBe('metric{job="testjob"} 3846 1443454528000'); expect(results[0].text).toBe('metric{job="testjob"} 3846 1443454528000');
@ -251,7 +243,7 @@ describe('PrometheusMetricFindQuery', () => {
}, },
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(1); expect(results).toHaveLength(1);
expect(results[0].text).toBe('2'); expect(results[0].text).toBe('2');
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -274,7 +266,7 @@ describe('PrometheusMetricFindQuery', () => {
], ],
}, },
}); });
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(results[0].text).toBe('up{instance="127.0.0.1:1234",job="job1"}'); expect(results[0].text).toBe('up{instance="127.0.0.1:1234",job="job1"}');
@ -309,7 +301,7 @@ describe('PrometheusMetricFindQuery', () => {
}, },
prometheusDatasource prometheusDatasource
); );
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);
@ -337,7 +329,7 @@ describe('PrometheusMetricFindQuery', () => {
}, },
prometheusDatasource prometheusDatasource
); );
const results = await query.process(); const results = await query.process(raw);
expect(results).toHaveLength(1); expect(results).toHaveLength(1);
expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledTimes(1);

View File

@ -2,8 +2,7 @@ import { chain, map as _map, uniq } from 'lodash';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { MetricFindValue, TimeRange } from '@grafana/data'; import { getDefaultTimeRange, MetricFindValue, TimeRange } from '@grafana/data';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { PrometheusDatasource } from './datasource'; import { PrometheusDatasource } from './datasource';
import { getPrometheusTime } from './language_utils'; import { getPrometheusTime } from './language_utils';
@ -24,10 +23,11 @@ export default class PrometheusMetricFindQuery {
) { ) {
this.datasource = datasource; this.datasource = datasource;
this.query = query; this.query = query;
this.range = getTimeSrv().timeRange(); this.range = getDefaultTimeRange();
} }
process(): Promise<MetricFindValue[]> { process(timeRange: TimeRange): Promise<MetricFindValue[]> {
this.range = timeRange;
const labelNamesRegex = PrometheusLabelNamesRegex; const labelNamesRegex = PrometheusLabelNamesRegex;
const labelNamesRegexWithMatch = PrometheusLabelNamesRegexWithMatch; const labelNamesRegexWithMatch = PrometheusLabelNamesRegexWithMatch;
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/; const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
@ -46,7 +46,7 @@ export default class PrometheusMetricFindQuery {
} }
if (labelNamesQuery) { if (labelNamesQuery) {
return this.datasource.getTagKeys({ filters: [] }); return this.datasource.getTagKeys({ filters: [], timeRange });
} }
const labelValuesQuery = this.query.match(labelValuesRegex); const labelValuesQuery = this.query.match(labelValuesRegex);

View File

@ -60,6 +60,7 @@ export function MetricsLabelsSection({
const onGetLabelNames = async (forLabel: Partial<QueryBuilderLabelFilter>): Promise<SelectableValue[]> => { const onGetLabelNames = async (forLabel: Partial<QueryBuilderLabelFilter>): Promise<SelectableValue[]> => {
// If no metric we need to use a different method // If no metric we need to use a different method
if (!query.metric) { if (!query.metric) {
// FIXME pass timeRange to fetchLabels method
await datasource.languageProvider.fetchLabels(); await datasource.languageProvider.fetchLabels();
return datasource.languageProvider.getLabelKeys().map((k) => ({ value: k })); return datasource.languageProvider.getLabelKeys().map((k) => ({ value: k }));
} }

View File

@ -318,7 +318,6 @@ function createDatasource(options?: Partial<DataSourceInstanceSettings<PromOptio
...options, ...options,
} as DataSourceInstanceSettings<PromOptions>, } as DataSourceInstanceSettings<PromOptions>,
undefined, undefined,
undefined,
languageProvider languageProvider
); );
return { datasource, languageProvider }; return { datasource, languageProvider };

View File

@ -44,7 +44,6 @@ function setup(queryOverrides: Partial<PromQuery> = {}) {
meta: {} as DataSourcePluginMeta, meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings, } as DataSourceInstanceSettings,
undefined, undefined,
undefined,
languageProvider languageProvider
); );

View File

@ -24,7 +24,6 @@ function createDatasource() {
meta: {} as DataSourcePluginMeta, meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings, } as DataSourceInstanceSettings,
undefined, undefined,
undefined,
languageProvider languageProvider
); );
return { datasource, languageProvider }; return { datasource, languageProvider };

View File

@ -84,7 +84,6 @@ const getDefaultDatasource = (jsonDataOverrides = {}) =>
readOnly: false, readOnly: false,
}, },
undefined, undefined,
undefined,
new EmptyLanguageProviderMock() as unknown as PromQlLanguageProvider new EmptyLanguageProviderMock() as unknown as PromQlLanguageProvider
); );

View File

@ -233,7 +233,6 @@ function createDatasource(withLabels?: boolean) {
meta: {} as DataSourcePluginMeta, meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings<PromOptions>, } as DataSourceInstanceSettings<PromOptions>,
undefined, undefined,
undefined,
languageProvider languageProvider
); );
return datasource; return datasource;

View File

@ -116,7 +116,6 @@ function createDatasource(withLabels?: boolean) {
meta: {} as DataSourcePluginMeta, meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings<PromOptions>, } as DataSourceInstanceSettings<PromOptions>,
undefined, undefined,
undefined,
languageProvider languageProvider
); );
return datasource; return datasource;

View File

@ -73,7 +73,6 @@ function setup(query: PromVisualQuery = defaultQuery) {
meta: {}, meta: {},
} as DataSourceInstanceSettings<PromOptions>, } as DataSourceInstanceSettings<PromOptions>,
undefined, undefined,
undefined,
languageProvider languageProvider
) as DataSourceApi, ) as DataSourceApi,
onRunQuery: () => {}, onRunQuery: () => {},

View File

@ -12,7 +12,6 @@ import { faro } from '@grafana/faro-web-sdk';
import { config, reportInteraction } from '@grafana/runtime/src'; import { config, reportInteraction } from '@grafana/runtime/src';
import { amendTable, Table, trimTable } from 'app/features/live/data/amendTimeSeries'; import { amendTable, Table, trimTable } from 'app/features/live/data/amendTimeSeries';
import { getTimeSrv } from '../../../../features/dashboard/services/TimeSrv';
import { PromQuery } from '../types'; import { PromQuery } from '../types';
// dashboardUID + panelId + refId // dashboardUID + panelId + refId
@ -260,7 +259,7 @@ export class QueryCache<T extends SupportedQueryTypes> {
let doPartialQuery = shouldCache; let doPartialQuery = shouldCache;
let prevTo: TimestampMs | undefined = undefined; let prevTo: TimestampMs | undefined = undefined;
const refreshIntervalMs = getTimeSrv().refreshMS; const refreshIntervalMs = request.intervalMs;
// pre-compute reqTargSigs // pre-compute reqTargSigs
const reqTargSigs = new Map<TargetIdent, TargetSig>(); const reqTargSigs = new Map<TargetIdent, TargetSig>();

View File

@ -4,8 +4,6 @@ import { map } from 'rxjs/operators';
import { CustomVariableSupport, DataQueryRequest, DataQueryResponse, rangeUtil } from '@grafana/data'; import { CustomVariableSupport, DataQueryRequest, DataQueryResponse, rangeUtil } from '@grafana/data';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { getTimeSrv, TimeSrv } from '../../../features/dashboard/services/TimeSrv';
import { PromVariableQueryEditor } from './components/VariableQueryEditor'; import { PromVariableQueryEditor } from './components/VariableQueryEditor';
import { PrometheusDatasource } from './datasource'; import { PrometheusDatasource } from './datasource';
import PrometheusMetricFindQuery from './metric_find_query'; import PrometheusMetricFindQuery from './metric_find_query';
@ -14,8 +12,7 @@ import { PromVariableQuery } from './types';
export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusDatasource> { export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusDatasource> {
constructor( constructor(
private readonly datasource: PrometheusDatasource, private readonly datasource: PrometheusDatasource,
private readonly templateSrv: TemplateSrv = getTemplateSrv(), private readonly templateSrv: TemplateSrv = getTemplateSrv()
private readonly timeSrv: TimeSrv = getTimeSrv()
) { ) {
super(); super();
} }
@ -49,12 +46,12 @@ export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusD
text: rangeUtil.intervalToMs(this.datasource.interval), text: rangeUtil.intervalToMs(this.datasource.interval),
value: rangeUtil.intervalToMs(this.datasource.interval), value: rangeUtil.intervalToMs(this.datasource.interval),
}, },
...this.datasource.getRangeScopedVars(this.timeSrv.timeRange()), ...this.datasource.getRangeScopedVars(request.range),
}; };
const interpolated = this.templateSrv.replace(query, scopedVars, this.datasource.interpolateQueryExpr); const interpolated = this.templateSrv.replace(query, scopedVars, this.datasource.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this.datasource, interpolated); const metricFindQuery = new PrometheusMetricFindQuery(this.datasource, interpolated);
const metricFindStream = from(metricFindQuery.process()); const metricFindStream = from(metricFindQuery.process(request.range));
return metricFindStream.pipe(map((results) => ({ data: results }))); return metricFindStream.pipe(map((results) => ({ data: results })));
} }