From 97a59a485522cf6894842b2688ed2b26e7b18974 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 11 Jun 2021 04:57:40 -0400 Subject: [PATCH] Loki: Add support for "label_values(log stream selector, label)" in templating (#35488) * Use series endpoint if we have queries expr * Update documentation --- docs/sources/datasources/loki.md | 9 ++-- .../datasource/loki/datasource.test.ts | 54 +++++++++---------- .../app/plugins/datasource/loki/datasource.ts | 33 ++++++++++-- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/docs/sources/datasources/loki.md b/docs/sources/datasources/loki.md index f74371c0181..0f1e1863620 100644 --- a/docs/sources/datasources/loki.md +++ b/docs/sources/datasources/loki.md @@ -148,10 +148,11 @@ Check out the [Templating]({{< relref "../variables/_index.md" >}}) documentatio Variable of the type _Query_ allows you to query Loki for a list labels or label values. The Loki data source plugin provides the following functions you can use in the `Query` input field. -| Name | Description | -| --------------------- | --------------------------------------------------------------- | -| `label_names()` | Returns a list of label names. | -| `label_values(label)` | Returns a list of label values for the `label` in every metric. | +| Name | Description | +| -------------------------------------------| -------------------------------------------------------------------------------------| +| `label_names()` | Returns a list of label names. | +| `label_values(label)` | Returns a list of label values for the `label`. | +| `label_values(log stream selector, label)` | Returns a list of label values for the `label` in the specified `log stream selector`.| ## Annotations diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index d94e8f02099..6ba91a80caf 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -548,42 +548,46 @@ describe('LokiDatasource', () => { describe('metricFindQuery', () => { const getTestContext = (mock: LokiDatasource) => { const ds = createLokiDSForTests(); - ds.getVersion = mock.getVersion; ds.metadataRequest = mock.metadataRequest; return { ds }; }; - const mocks = makeMetadataAndVersionsMocks(); + const mock = makeMockLokiDatasource( + { label1: ['value1', 'value2'], label2: ['value3', 'value4'] }, + { '{label1="value1", label2="value2"}': [{ label5: 'value5' }] } + ); - mocks.forEach((mock, index) => { - it(`should return label names for Loki v${index}`, async () => { - const { ds } = getTestContext(mock); + it(`should return label names for Loki`, async () => { + const { ds } = getTestContext(mock); - const res = await ds.metricFindQuery('label_names()'); + const res = await ds.metricFindQuery('label_names()'); - expect(res).toEqual([{ text: 'label1' }, { text: 'label2' }]); - }); + expect(res).toEqual([{ text: 'label1' }, { text: 'label2' }]); }); - mocks.forEach((mock, index) => { - it(`should return label values for Loki v${index}`, async () => { - const { ds } = getTestContext(mock); + it(`should return label values for Loki when no matcher`, async () => { + const { ds } = getTestContext(mock); - const res = await ds.metricFindQuery('label_values(label1)'); + const res = await ds.metricFindQuery('label_values(label1)'); - expect(res).toEqual([{ text: 'value1' }, { text: 'value2' }]); - }); + expect(res).toEqual([{ text: 'value1' }, { text: 'value2' }]); }); - mocks.forEach((mock, index) => { - it(`should return empty array when incorrect query for Loki v${index}`, async () => { - const { ds } = getTestContext(mock); + it(`should return label values for Loki with matcher`, async () => { + const { ds } = getTestContext(mock); - const res = await ds.metricFindQuery('incorrect_query'); + const res = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)'); - expect(res).toEqual([]); - }); + expect(res).toEqual([{ text: 'value5' }]); + }); + + it(`should return empty array when incorrect query for Loki`, async () => { + const { ds } = getTestContext(mock); + + const res = await ds.metricFindQuery('incorrect_query'); + + expect(res).toEqual([]); }); }); }); @@ -629,13 +633,3 @@ function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest { const labelNamesRegex = /^label_names\(\)\s*$/; const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/; - const params = this.getTimeRangeParams(); const labelNames = query.match(labelNamesRegex); if (labelNames) { - return await this.labelNamesQuery(params); + return await this.labelNamesQuery(); } const labelValues = query.match(labelValuesRegex); if (labelValues) { - return await this.labelValuesQuery(labelValues[2], params); + // If we have query expr, use /series endpoint + if (labelValues[1]) { + return await this.labelValuesSeriesQuery(labelValues[1], labelValues[2]); + } + return await this.labelValuesQuery(labelValues[2]); } return Promise.resolve([]); } - async labelNamesQuery(params?: Record) { + async labelNamesQuery() { const url = `${LOKI_ENDPOINT}/label`; + const params = this.getTimeRangeParams(); const result = await this.metadataRequest(url, params); return result.map((value: string) => ({ text: value })); } - async labelValuesQuery(label: string, params?: Record) { + async labelValuesQuery(label: string) { + const params = this.getTimeRangeParams(); const url = `${LOKI_ENDPOINT}/label/${label}/values`; const result = await this.metadataRequest(url, params); return result.map((value: string) => ({ text: value })); } + async labelValuesSeriesQuery(expr: string, label: string) { + const timeParams = this.getTimeRangeParams(); + const params = { + ...timeParams, + match: expr, + }; + const url = `${LOKI_ENDPOINT}/series`; + const streams = new Set(); + const result = await this.metadataRequest(url, params); + result.forEach((stream: { [key: string]: string }) => { + if (stream[label]) { + streams.add({ text: stream[label] }); + } + }); + + return Array.from(streams); + } + interpolateQueryExpr(value: any, variable: any) { // if no multi or include all do not regexEscape if (!variable.multi && !variable.includeAll) {