Commit 24635834 by kay delaney Committed by GitHub

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
parent 8f5df801
......@@ -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,
......
......@@ -250,6 +250,7 @@ export function initializeExplore(
ui,
})
);
dispatch(updateTimeRangeAction({ exploreId }));
};
}
......
......@@ -9,7 +9,8 @@ const LokiQueryField: FunctionComponent<LokiQueryFieldFormProps> = ({
}) => {
const { isSyntaxReady, setActiveOption, refreshLabels, ...syntaxProps } = useLokiSyntax(
datasource.languageProvider,
datasourceStatus
datasourceStatus,
otherProps.absoluteRange
);
return (
......
......@@ -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);
......
......@@ -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();
......
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);
......
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);
......
......@@ -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) {
......
......@@ -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;
});
......
// @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();
});
});
......@@ -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();
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment