浏览代码

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
kay delaney 6 年之前
父节点
当前提交
246358344c

+ 6 - 1
public/app/features/explore/QueryRow.tsx

@@ -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,

+ 1 - 0
public/app/features/explore/state/actions.ts

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

+ 2 - 1
public/app/plugins/datasource/loki/components/LokiQueryField.tsx

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

+ 4 - 2
public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx

@@ -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);

+ 21 - 4
public/app/plugins/datasource/loki/components/useLokiLabels.test.ts

@@ -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();

+ 4 - 2
public/app/plugins/datasource/loki/components/useLokiLabels.ts

@@ -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);

+ 15 - 3
public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts

@@ -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);
 

+ 8 - 2
public/app/plugins/datasource/loki/components/useLokiSyntax.ts

@@ -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) {

+ 3 - 2
public/app/plugins/datasource/loki/datasource.ts

@@ -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;
     });

+ 77 - 23
public/app/plugins/datasource/loki/language_provider.test.ts

@@ -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();
   });
 });

+ 21 - 14
public/app/plugins/datasource/loki/language_provider.ts

@@ -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();
 

+ 1 - 1
public/app/types/explore.ts

@@ -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.