Explore: Adds support for new loki 'start' and 'end' params for labels endpoint (#17512)

* Explore: Adds support for new loki 'start' and 'end' params for labels endpoint
Also initializes absoluteRange when explore is initialized
Closes #16788

* Explore: Dispatches updateTimeRangeAction instead of passing absoluteRange when initializing
Also removes dependency on sinon from loki language provider test

* Loki: Refactors transformation of absolute time range to URL params into small utility function

* Makes use of rangeToParams() util function in loki language provider test
Also updates LanguageProvider.request() interface so that url should be type string, and adds optional params argument
This commit is contained in:
kay delaney 2019-07-08 16:14:48 +01:00 committed by GitHub
parent 8f5df80161
commit 246358344c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 163 additions and 55 deletions

View File

@ -13,7 +13,7 @@ import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/act
// Types
import { StoreState } from 'app/types';
import { TimeRange } from '@grafana/data';
import { TimeRange, AbsoluteTimeRange } from '@grafana/data';
import { DataQuery, DataSourceApi, QueryFixAction, DataSourceStatus, PanelData, DataQueryError } from '@grafana/ui';
import { HistoryItem, ExploreItemState, ExploreId, ExploreMode } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
@ -38,6 +38,7 @@ interface QueryRowProps extends PropsFromParent {
query: DataQuery;
modifyQueries: typeof modifyQueries;
range: TimeRange;
absoluteRange: AbsoluteTimeRange;
removeQueryRowAction: typeof removeQueryRowAction;
runQueries: typeof runQueries;
queryResponse: PanelData;
@ -116,6 +117,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
query,
exploreEvents,
range,
absoluteRange,
datasourceStatus,
queryResponse,
latency,
@ -148,6 +150,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
onChange={this.onChange}
panelData={null}
queryResponse={queryResponse}
absoluteRange={absoluteRange}
/>
) : (
<QueryEditor
@ -202,6 +205,7 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps)
history,
queries,
range,
absoluteRange,
datasourceError,
graphResult,
loadingState,
@ -224,6 +228,7 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps)
history,
query,
range,
absoluteRange,
datasourceStatus,
queryResponse,
latency,

View File

@ -250,6 +250,7 @@ export function initializeExplore(
ui,
})
);
dispatch(updateTimeRangeAction({ exploreId }));
};
}

View File

@ -9,7 +9,8 @@ const LokiQueryField: FunctionComponent<LokiQueryFieldFormProps> = ({
}) => {
const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax(
datasource.languageProvider,
datasourceStatus
datasourceStatus,
otherProps.absoluteRange
);
return (

View File

@ -17,6 +17,7 @@ import BracesPlugin from 'app/features/explore/slate-plugins/braces';
import { LokiQuery } from '../types';
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
import { DataSourceApi, ExploreQueryFieldProps, DataSourceStatus } from '@grafana/ui';
import { AbsoluteTimeRange } from '@grafana/data';
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean, datasourceStatus: DataSourceStatus) {
if (datasourceStatus === DataSourceStatus.Disconnected) {
@ -70,6 +71,7 @@ export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<DataSour
syntax: any;
logLabelOptions: any[];
syntaxLoaded: any;
absoluteRange: AbsoluteTimeRange;
onLoadOptions: (selectedOptions: CascaderOption[]) => void;
onLabelsRefresh?: () => void;
}
@ -123,7 +125,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
return { suggestions: [] };
}
const { history } = this.props;
const { history, absoluteRange } = this.props;
const { prefix, text, value, wrapperNode } = typeahead;
// Get DOM-dependent context
@ -134,7 +136,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
const result = datasource.languageProvider.provideCompletionItems(
{ text, value, prefix, wrapperClasses, labelKey },
{ history }
{ history, absoluteRange }
);
console.log('handleTypeahead', wrapperClasses, text, prefix, nextChar, labelKey, result.context);

View File

@ -2,6 +2,7 @@ import { renderHook, act } from 'react-hooks-testing-library';
import LanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiLabels } from './useLokiLabels';
import { DataSourceStatus } from '@grafana/ui/src/types/datasource';
import { AbsoluteTimeRange } from '@grafana/data';
describe('useLokiLabels hook', () => {
it('should refresh labels', async () => {
@ -10,6 +11,10 @@ describe('useLokiLabels hook', () => {
};
const languageProvider = new LanguageProvider(datasource);
const logLabelOptionsMock = ['Holy mock!'];
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560153109000,
};
languageProvider.refreshLogLabels = () => {
languageProvider.logLabelOptions = logLabelOptionsMock;
@ -17,7 +22,7 @@ describe('useLokiLabels hook', () => {
};
const { result, waitForNextUpdate } = renderHook(() =>
useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Connected)
useLokiLabels(languageProvider, true, [], rangeMock, DataSourceStatus.Connected, DataSourceStatus.Connected)
);
act(() => result.current.refreshLabels());
expect(result.current.logLabelOptions).toEqual([]);
@ -29,26 +34,38 @@ describe('useLokiLabels hook', () => {
const datasource = {
metadataRequest: () => ({ data: { data: [] as any[] } }),
};
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560153109000,
};
const languageProvider = new LanguageProvider(datasource);
languageProvider.refreshLogLabels = jest.fn();
renderHook(() =>
useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Disconnected)
useLokiLabels(languageProvider, true, [], rangeMock, DataSourceStatus.Connected, DataSourceStatus.Disconnected)
);
expect(languageProvider.refreshLogLabels).toBeCalledTimes(1);
expect(languageProvider.refreshLogLabels).toBeCalledWith(true);
expect(languageProvider.refreshLogLabels).toBeCalledWith(rangeMock, true);
});
it('should not force refresh labels after a connect', () => {
const datasource = {
metadataRequest: () => ({ data: { data: [] as any[] } }),
};
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560153109000,
};
const languageProvider = new LanguageProvider(datasource);
languageProvider.refreshLogLabels = jest.fn();
renderHook(() =>
useLokiLabels(languageProvider, true, [], DataSourceStatus.Disconnected, DataSourceStatus.Connected)
useLokiLabels(languageProvider, true, [], rangeMock, DataSourceStatus.Disconnected, DataSourceStatus.Connected)
);
expect(languageProvider.refreshLogLabels).not.toBeCalled();

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { DataSourceStatus } from '@grafana/ui/src/types/datasource';
import { AbsoluteTimeRange } from '@grafana/data';
import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
@ -17,6 +18,7 @@ export const useLokiLabels = (
languageProvider: LokiLanguageProvider,
languageProviderInitialised: boolean,
activeOption: CascaderOption[],
absoluteRange: AbsoluteTimeRange,
datasourceStatus: DataSourceStatus,
initialDatasourceStatus?: DataSourceStatus // used for test purposes
) => {
@ -32,14 +34,14 @@ export const useLokiLabels = (
// Async
const fetchOptionValues = async (option: string) => {
await languageProvider.fetchLabelValues(option);
await languageProvider.fetchLabelValues(option, absoluteRange);
if (mounted.current) {
setLogLabelOptions(languageProvider.logLabelOptions);
}
};
const tryLabelsRefresh = async () => {
await languageProvider.refreshLogLabels(shouldForceRefreshLabels);
await languageProvider.refreshLogLabels(absoluteRange, shouldForceRefreshLabels);
if (mounted.current) {
setRefreshLabels(false);

View File

@ -1,5 +1,6 @@
import { renderHook, act } from 'react-hooks-testing-library';
import { DataSourceStatus } from '@grafana/ui/src/types/datasource';
import { AbsoluteTimeRange } from '@grafana/data';
import LanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiSyntax } from './useLokiSyntax';
@ -14,6 +15,11 @@ describe('useLokiSyntax hook', () => {
const logLabelOptionsMock2 = ['Mock the hell?!'];
const logLabelOptionsMock3 = ['Oh my mock!'];
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560163909000,
};
languageProvider.refreshLogLabels = () => {
languageProvider.logLabelOptions = logLabelOptionsMock;
return Promise.resolve();
@ -30,7 +36,9 @@ describe('useLokiSyntax hook', () => {
};
it('should provide Loki syntax when used', async () => {
const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected));
const { result, waitForNextUpdate } = renderHook(() =>
useLokiSyntax(languageProvider, DataSourceStatus.Connected, rangeMock)
);
expect(result.current.syntax).toEqual(null);
await waitForNextUpdate();
@ -39,7 +47,9 @@ describe('useLokiSyntax hook', () => {
});
it('should fetch labels on first call', async () => {
const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected));
const { result, waitForNextUpdate } = renderHook(() =>
useLokiSyntax(languageProvider, DataSourceStatus.Connected, rangeMock)
);
expect(result.current.isSyntaxReady).toBeFalsy();
expect(result.current.logLabelOptions).toEqual([]);
@ -50,7 +60,9 @@ describe('useLokiSyntax hook', () => {
});
it('should try to fetch missing options when active option changes', async () => {
const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected));
const { result, waitForNextUpdate } = renderHook(() =>
useLokiSyntax(languageProvider, DataSourceStatus.Connected, rangeMock)
);
await waitForNextUpdate();
expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2);

View File

@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
// @ts-ignore
import Prism from 'prismjs';
import { DataSourceStatus } from '@grafana/ui/src/types/datasource';
import { AbsoluteTimeRange } from '@grafana/data';
import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels';
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
@ -15,7 +15,11 @@ const PRISM_SYNTAX = 'promql';
* @param languageProvider
* @description Initializes given language provider, exposes Loki syntax and enables loading label option values
*/
export const useLokiSyntax = (languageProvider: LokiLanguageProvider, datasourceStatus: DataSourceStatus) => {
export const useLokiSyntax = (
languageProvider: LokiLanguageProvider,
datasourceStatus: DataSourceStatus,
absoluteRange: AbsoluteTimeRange
) => {
const mounted = useRefMounted();
// State
const [languageProviderInitialized, setLanguageProviderInitilized] = useState(false);
@ -32,11 +36,13 @@ export const useLokiSyntax = (languageProvider: LokiLanguageProvider, datasource
languageProvider,
languageProviderInitialized,
activeOption,
absoluteRange,
datasourceStatus
);
// Async
const initializeLanguageProvider = async () => {
languageProvider.initialRange = absoluteRange;
await languageProvider.start();
Prism.languages[PRISM_SYNTAX] = languageProvider.getSyntax();
if (mounted.current) {

View File

@ -78,6 +78,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...options,
url,
};
return this.backendSrv.datasourceRequest(req);
}
@ -254,10 +255,10 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
return this.languageProvider.importQueries(queries, originMeta.id);
}
metadataRequest(url: string) {
metadataRequest(url: string, params?: any) {
// HACK to get label values for {job=|}, will be replaced when implementing LokiQueryField
const apiUrl = url.replace('v1', 'prom');
return this._request(apiUrl, { silent: true }).then((res: DataQueryResponse) => {
return this._request(apiUrl, params, { silent: true }).then((res: DataQueryResponse) => {
const data: any = { data: { data: res.data.values || [] } };
return data;
});

View File

@ -1,7 +1,8 @@
// @ts-ignore
import Plain from 'slate-plain-serializer';
import LanguageProvider, { LABEL_REFRESH_INTERVAL } from './language_provider';
import LanguageProvider, { LABEL_REFRESH_INTERVAL, rangeToParams } from './language_provider';
import { AbsoluteTimeRange } from '@grafana/data';
import { advanceTo, clear, advanceBy } from 'jest-date-mock';
import { beforeEach } from 'test/lib/common';
import { DataQueryResponseData } from '@grafana/ui';
@ -11,8 +12,13 @@ describe('Language completion provider', () => {
metadataRequest: () => ({ data: { data: [] as DataQueryResponseData[] } }),
};
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560163909000,
};
describe('empty query suggestions', () => {
it('returns no suggestions on emtpty context', () => {
it('returns no suggestions on empty context', () => {
const instance = new LanguageProvider(datasource);
const value = Plain.deserialize('');
const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
@ -21,7 +27,7 @@ describe('Language completion provider', () => {
expect(result.suggestions.length).toEqual(0);
});
it('returns default suggestions with history on emtpty context when history was provided', () => {
it('returns default suggestions with history on empty context when history was provided', () => {
const instance = new LanguageProvider(datasource);
const value = Plain.deserialize('');
const history = [
@ -29,7 +35,10 @@ describe('Language completion provider', () => {
query: { refId: '1', expr: '{app="foo"}' },
},
];
const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }, { history });
const result = instance.provideCompletionItems(
{ text: '', prefix: '', value, wrapperClasses: [] },
{ history, absoluteRange: rangeMock }
);
expect(result.context).toBeUndefined();
expect(result.refresher).toBeUndefined();
expect(result.suggestions).toMatchObject([
@ -79,64 +88,102 @@ describe('Language completion provider', () => {
anchorOffset: 1,
});
const valueWithSelection = value.change().select(range).value;
const result = instance.provideCompletionItems({
text: '',
prefix: '',
wrapperClasses: ['context-labels'],
value: valueWithSelection,
});
const result = instance.provideCompletionItems(
{
text: '',
prefix: '',
wrapperClasses: ['context-labels'],
value: valueWithSelection,
},
{ absoluteRange: rangeMock }
);
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'job' }, { label: 'namespace' }], label: 'Labels' }]);
});
});
});
describe('Request URL', () => {
it('should contain range params', async () => {
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560163909000,
};
const datasourceWithLabels = {
metadataRequest: url => {
if (url.slice(0, 15) === '/api/prom/label') {
return { data: { data: ['other'] } };
} else {
return { data: { data: [] } };
}
},
};
const datasourceSpy = jest.spyOn(datasourceWithLabels, 'metadataRequest');
const instance = new LanguageProvider(datasourceWithLabels, { initialRange: rangeMock });
await instance.refreshLogLabels(rangeMock, true);
const expectedUrl = '/api/prom/label';
expect(datasourceSpy).toHaveBeenCalledWith(expectedUrl, rangeToParams(rangeMock));
});
});
describe('Query imports', () => {
const datasource = {
metadataRequest: () => ({ data: { data: [] as DataQueryResponseData[] } }),
};
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560163909000,
};
it('returns empty queries for unknown origin datasource', async () => {
const instance = new LanguageProvider(datasource);
const instance = new LanguageProvider(datasource, { initialRange: rangeMock });
const result = await instance.importQueries([{ refId: 'bar', expr: 'foo' }], 'unknown');
expect(result).toEqual([{ refId: 'bar', expr: '' }]);
});
describe('prometheus query imports', () => {
it('returns empty query from metric-only query', async () => {
const instance = new LanguageProvider(datasource);
const instance = new LanguageProvider(datasource, { initialRange: rangeMock });
const result = await instance.importPrometheusQuery('foo');
expect(result).toEqual('');
});
it('returns empty query from selector query if label is not available', async () => {
const datasourceWithLabels = {
metadataRequest: (url: string) =>
url === '/api/prom/label' ? { data: { data: ['other'] } } : { data: { data: [] as DataQueryResponseData[] } },
metadataRequest: url =>
url.slice(0, 15) === '/api/prom/label'
? { data: { data: ['other'] } }
: { data: { data: [] as DataQueryResponseData[] } },
};
const instance = new LanguageProvider(datasourceWithLabels);
const instance = new LanguageProvider(datasourceWithLabels, { initialRange: rangeMock });
const result = await instance.importPrometheusQuery('{foo="bar"}');
expect(result).toEqual('{}');
});
it('returns selector query from selector query with common labels', async () => {
const datasourceWithLabels = {
metadataRequest: (url: string) =>
url === '/api/prom/label' ? { data: { data: ['foo'] } } : { data: { data: [] as DataQueryResponseData[] } },
metadataRequest: url =>
url.slice(0, 15) === '/api/prom/label'
? { data: { data: ['foo'] } }
: { data: { data: [] as DataQueryResponseData[] } },
};
const instance = new LanguageProvider(datasourceWithLabels);
const instance = new LanguageProvider(datasourceWithLabels, { initialRange: rangeMock });
const result = await instance.importPrometheusQuery('metric{foo="bar",baz="42"}');
expect(result).toEqual('{foo="bar"}');
});
it('returns selector query from selector query with all labels if logging label list is empty', async () => {
const datasourceWithLabels = {
metadataRequest: (url: string) =>
url === '/api/prom/label'
metadataRequest: url =>
url.slice(0, 15) === '/api/prom/label'
? { data: { data: [] as DataQueryResponseData[] } }
: { data: { data: [] as DataQueryResponseData[] } },
};
const instance = new LanguageProvider(datasourceWithLabels);
const instance = new LanguageProvider(datasourceWithLabels, { initialRange: rangeMock });
const result = await instance.importPrometheusQuery('metric{foo="bar",baz="42"}');
expect(result).toEqual('{baz="42",foo="bar"}');
});
@ -149,6 +196,11 @@ describe('Labels refresh', () => {
};
const instance = new LanguageProvider(datasource);
const rangeMock: AbsoluteTimeRange = {
from: 1560153109000,
to: 1560163909000,
};
beforeEach(() => {
instance.fetchLogLabels = jest.fn();
});
@ -157,18 +209,20 @@ describe('Labels refresh', () => {
jest.clearAllMocks();
clear();
});
it("should not refresh labels if refresh interval hasn't passed", () => {
advanceTo(new Date(2019, 1, 1, 0, 0, 0));
instance.logLabelFetchTs = Date.now();
advanceBy(LABEL_REFRESH_INTERVAL / 2);
instance.refreshLogLabels();
instance.refreshLogLabels(rangeMock);
expect(instance.fetchLogLabels).not.toBeCalled();
});
it('should refresh labels if refresh interval passed', () => {
advanceTo(new Date(2019, 1, 1, 0, 0, 0));
instance.logLabelFetchTs = Date.now();
advanceBy(LABEL_REFRESH_INTERVAL + 1);
instance.refreshLogLabels();
instance.refreshLogLabels(rangeMock);
expect(instance.fetchLogLabels).toBeCalled();
});
});

View File

@ -15,16 +15,18 @@ import {
HistoryItem,
} from 'app/types/explore';
import { LokiQuery } from './types';
import { dateTime } from '@grafana/data';
import { dateTime, AbsoluteTimeRange } from '@grafana/data';
import { PromQuery } from '../prometheus/types';
const DEFAULT_KEYS = ['job', 'namespace'];
const EMPTY_SELECTOR = '{}';
const HISTORY_ITEM_COUNT = 10;
const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h
const NS_IN_MS = 1_000_000;
export const LABEL_REFRESH_INTERVAL = 1000 * 30; // 30sec
const wrapLabel = (label: string) => ({ label });
export const rangeToParams = (range: AbsoluteTimeRange) => ({ start: range.from * NS_IN_MS, end: range.to * NS_IN_MS });
type LokiHistoryItem = HistoryItem<LokiQuery>;
@ -50,6 +52,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
logLabelOptions: any[];
logLabelFetchTs?: number;
started: boolean;
initialRange: AbsoluteTimeRange;
constructor(datasource: any, initialValues?: any) {
super();
@ -67,13 +70,13 @@ export default class LokiLanguageProvider extends LanguageProvider {
return syntax;
}
request = (url: string) => {
return this.datasource.metadataRequest(url);
request = (url: string, params?: any) => {
return this.datasource.metadataRequest(url, params);
};
start = () => {
if (!this.startTask) {
this.startTask = this.fetchLogLabels();
this.startTask = this.fetchLogLabels(this.initialRange);
}
return this.startTask;
};
@ -120,7 +123,10 @@ export default class LokiLanguageProvider extends LanguageProvider {
return { suggestions };
}
getLabelCompletionItems({ text, wrapperClasses, labelKey, value }: TypeaheadInput): TypeaheadOutput {
getLabelCompletionItems(
{ text, wrapperClasses, labelKey, value }: TypeaheadInput,
{ absoluteRange }: any
): TypeaheadOutput {
let context: string;
let refresher: Promise<any> = null;
const suggestions: CompletionItemGroup[] = [];
@ -146,7 +152,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
items: labelValues.map(wrapLabel),
});
} else {
refresher = this.fetchLabelValues(labelKey);
refresher = this.fetchLabelValues(labelKey, absoluteRange);
}
}
} else {
@ -206,7 +212,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
if (existingKeys && existingKeys.length > 0) {
// Check for common labels
for (const key in labels) {
if (existingKeys && existingKeys.indexOf(key) > -1) {
if (existingKeys && existingKeys.includes(key)) {
// Should we check for label value equality here?
labelsToKeep[key] = labels[key];
}
@ -227,11 +233,12 @@ export default class LokiLanguageProvider extends LanguageProvider {
return '';
}
async fetchLogLabels(): Promise<any> {
async fetchLogLabels(absoluteRange: AbsoluteTimeRange): Promise<any> {
const url = '/api/prom/label';
try {
this.logLabelFetchTs = Date.now();
const res = await this.request(url);
const res = await this.request(url, rangeToParams(absoluteRange));
const body = await (res.data || res.json());
const labelKeys = body.data.slice().sort();
this.labelKeys = {
@ -244,7 +251,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
return Promise.all(
labelKeys
.filter((key: string) => DEFAULT_KEYS.indexOf(key) > -1)
.map((key: string) => this.fetchLabelValues(key))
.map((key: string) => this.fetchLabelValues(key, absoluteRange))
);
} catch (e) {
console.error(e);
@ -252,16 +259,16 @@ export default class LokiLanguageProvider extends LanguageProvider {
return [];
}
async refreshLogLabels(forceRefresh?: boolean) {
async refreshLogLabels(absoluteRange: AbsoluteTimeRange, forceRefresh?: boolean) {
if ((this.labelKeys && Date.now() - this.logLabelFetchTs > LABEL_REFRESH_INTERVAL) || forceRefresh) {
await this.fetchLogLabels();
await this.fetchLogLabels(absoluteRange);
}
}
async fetchLabelValues(key: string) {
async fetchLabelValues(key: string, absoluteRange: AbsoluteTimeRange) {
const url = `/api/prom/label/${key}/values`;
try {
const res = await this.request(url);
const res = await this.request(url, rangeToParams(absoluteRange));
const body = await (res.data || res.json());
const values = body.data.slice().sort();

View File

@ -286,7 +286,7 @@ export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
export abstract class LanguageProvider {
datasource: any;
request: (url: any) => Promise<any>;
request: (url: string, params?: any) => Promise<any>;
/**
* 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.