Commit 54a3f5fd by David Committed by GitHub

Loki: use series API for stream facetting (#21332)

parent 2cf538a4
......@@ -56,11 +56,43 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
"languageProvider": LokiLanguageProvider {
"cleanText": [Function],
"datasource": [Circular],
"fetchSeriesLabels": [Function],
"getBeginningCompletionItems": [Function],
"getTermCompletionItems": [Function],
"labelKeys": Object {},
"labelValues": Object {},
"labelKeys": Array [],
"labelsCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"request": [Function],
"seriesCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"start": [Function],
},
"metadataRequest": [Function],
......
......@@ -59,9 +59,9 @@ describe('useLokiSyntax hook', () => {
await waitForNextUpdate();
expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2);
languageProvider.fetchLabelValues = (key: string) => {
languageProvider.fetchLabelValues = (key: string, absoluteRange: AbsoluteTimeRange) => {
languageProvider.logLabelOptions = logLabelOptionsMock3;
return Promise.resolve();
return Promise.resolve([]);
};
act(() => result.current.setActiveOption([activeOptionMock]));
......
......@@ -390,9 +390,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async metadataRequest(url: string, params?: Record<string, string>) {
const res = await this._request(url, params, { silent: true }).toPromise();
return {
data: { data: res.data.data || res.data.values || [] },
};
return res.data.data || res.data.values || [];
}
async metricFindQuery(query: string) {
......@@ -423,7 +421,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async labelNamesQuery() {
const url = (await this.getVersion()) === 'v0' ? `${LEGACY_LOKI_ENDPOINT}/label` : `${LOKI_ENDPOINT}/label`;
const result = await this.metadataRequest(url);
return result.data.data.map((value: string) => ({ text: value }));
return result.map((value: string) => ({ text: value }));
}
async labelValuesQuery(label: string) {
......@@ -432,7 +430,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
? `${LEGACY_LOKI_ENDPOINT}/label/${label}/values`
: `${LOKI_ENDPOINT}/label/${label}/values`;
const result = await this.metadataRequest(url);
return result.data.data.map((value: string) => ({ text: value }));
return result.map((value: string) => ({ text: value }));
}
interpolateQueryExpr(value: any, variable: any) {
......
import Plain from 'slate-plain-serializer';
import { Editor as SlateEditor } from 'slate';
import LanguageProvider, { LABEL_REFRESH_INTERVAL, LokiHistoryItem, rangeToParams } from './language_provider';
import { AbsoluteTimeRange } from '@grafana/data';
......@@ -85,34 +84,53 @@ describe('Language completion provider', () => {
});
});
describe('label suggestions', () => {
it('returns default label suggestions on label context', async () => {
const instance = new LanguageProvider(datasource);
const value = Plain.deserialize('{}');
const ed = new SlateEditor({ value });
const valueWithSelection = ed.moveForward(1).value;
const result = await instance.provideCompletionItems(
{
text: '',
prefix: '',
wrapperClasses: ['context-labels'],
value: valueWithSelection,
},
{ absoluteRange: rangeMock }
);
describe('label key suggestions', () => {
it('returns all label suggestions on empty selector', async () => {
const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{}', '', '', 1);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'job' }, { label: 'namespace' }], label: 'Labels' }]);
expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }, { label: 'label2' }], label: 'Labels' }]);
});
it('returns label suggestions from Loki', async () => {
it('returns all label suggestions on selector when starting to type', async () => {
const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{}', '');
const input = createTypeaheadInput('{l}', '', '', 2);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }, { label: 'label2' }], label: 'Labels' }]);
});
});
describe('label suggestions facetted', () => {
it('returns facetted label suggestions based on selector', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{foo="bar"}': [{ label1: 'label_val1' }] }
);
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{foo="bar",}', '', '', 11);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }], label: 'Labels' }]);
});
it('returns facetted label suggestions for multipule selectors', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{baz="42",foo="bar"}': [{ label2: 'label_val2' }] }
);
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{baz="42",foo="bar",}', '', '', 20);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label2' }], label: 'Labels' }]);
});
});
describe('label suggestions', () => {
it('returns label values suggestions from Loki', async () => {
const datasource = makeMockLokiDatasource({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
......
......@@ -3,34 +3,45 @@ import { DataSourceSettings } from '@grafana/data';
import { LokiOptions } from './types';
import { createDatasourceSettings } from '../../../features/datasources/mocks';
export function makeMockLokiDatasource(labelsAndValues: { [label: string]: string[] }): LokiDatasource {
interface Labels {
[label: string]: string[];
}
interface Series {
[label: string]: string;
}
interface SeriesForSelector {
[selector: string]: Series[];
}
export function makeMockLokiDatasource(labelsAndValues: Labels, series?: SeriesForSelector): LokiDatasource {
const legacyLokiLabelsAndValuesEndpointRegex = /^\/api\/prom\/label\/(\w*)\/values/;
const lokiLabelsAndValuesEndpointRegex = /^\/loki\/api\/v1\/label\/(\w*)\/values/;
const lokiSeriesEndpointRegex = /^\/loki\/api\/v1\/series/;
const legacyLokiLabelsEndpoint = `${LEGACY_LOKI_ENDPOINT}/label`;
const lokiLabelsEndpoint = `${LOKI_ENDPOINT}/label`;
const labels = Object.keys(labelsAndValues);
return {
metadataRequest: (url: string) => {
let responseData;
metadataRequest: (url: string, params?: { [key: string]: string }) => {
if (url === legacyLokiLabelsEndpoint || url === lokiLabelsEndpoint) {
responseData = labels;
return labels;
} else {
const match = url.match(legacyLokiLabelsAndValuesEndpointRegex) || url.match(lokiLabelsAndValuesEndpointRegex);
if (match) {
responseData = labelsAndValues[match[1]];
const legacyLabelsMatch = url.match(legacyLokiLabelsAndValuesEndpointRegex);
const labelsMatch = url.match(lokiLabelsAndValuesEndpointRegex);
const seriesMatch = url.match(lokiSeriesEndpointRegex);
if (legacyLabelsMatch) {
return labelsAndValues[legacyLabelsMatch[1]] || [];
} else if (labelsMatch) {
return labelsAndValues[labelsMatch[1]] || [];
} else if (seriesMatch) {
return series[params.match] || [];
} else {
throw new Error(`Unexpected url error, ${url}`);
}
}
if (responseData) {
return {
data: {
data: responseData,
},
};
} else {
throw new Error(`Unexpected url error, ${url}`);
}
},
} as any;
}
......
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