From f5d04a067eb151004815eae5345371897410b343 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:16:35 +0100 Subject: [PATCH] Loki: Add optional stream selector to fetchLabelValues API (#77207) * Loki: Add optional stream selector to fetchLabelValues API * Update public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md Co-authored-by: Sven Grossmann * Update tests --------- Co-authored-by: Sven Grossmann --- .../datasource/loki/LanguageProvider.test.ts | 53 ++++++++++++++++++- .../datasource/loki/LanguageProvider.ts | 23 +++++--- .../app_plugin_developer_documentation.md | 19 ++++++- 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index 545aea67847..39d845e45d8 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -233,7 +233,23 @@ describe('Language completion provider', () => { const provider = await getLanguageProvider(datasource); const requestSpy = jest.spyOn(provider, 'request'); const labelValues = await provider.fetchLabelValues('testkey'); - expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', { + end: 1560163909000, + start: 1560153109000, + }); + expect(labelValues).toEqual(['label1_val1', 'label1_val2']); + }); + + it('fetch label when options.streamSelector provided and values is not cached', async () => { + const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] }); + const provider = await getLanguageProvider(datasource); + const requestSpy = jest.spyOn(provider, 'request'); + const labelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' }); + expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', { + end: 1560163909000, + query: '%7Bfoo%3D%22bar%22%7D', + start: 1560153109000, + }); expect(labelValues).toEqual(['label1_val1', 'label1_val2']); }); @@ -247,6 +263,28 @@ describe('Language completion provider', () => { const nextLabelValues = await provider.fetchLabelValues('testkey'); expect(requestSpy).toHaveBeenCalledTimes(1); + expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', { + end: 1560163909000, + start: 1560153109000, + }); + expect(nextLabelValues).toEqual(['label1_val1', 'label1_val2']); + }); + + it('should return cached values when options.streamSelector provided', async () => { + const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] }); + const provider = await getLanguageProvider(datasource); + const requestSpy = jest.spyOn(provider, 'request'); + const labelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' }); + expect(requestSpy).toHaveBeenCalledTimes(1); + expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', { + end: 1560163909000, + query: '%7Bfoo%3D%22bar%22%7D', + start: 1560153109000, + }); + expect(labelValues).toEqual(['label1_val1', 'label1_val2']); + + const nextLabelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' }); + expect(requestSpy).toHaveBeenCalledTimes(1); expect(nextLabelValues).toEqual(['label1_val1', 'label1_val2']); }); @@ -258,6 +296,19 @@ describe('Language completion provider', () => { expect(requestSpy).toHaveBeenCalledWith('label/%60%5C%22testkey/values', expect.any(Object)); }); + + it('should encode special characters in options.streamSelector', async () => { + const datasource = setup({ '`\\"testkey': ['label1_val1', 'label1_val2'], label2: [] }); + const provider = await getLanguageProvider(datasource); + const requestSpy = jest.spyOn(provider, 'request'); + await provider.fetchLabelValues('`\\"testkey', { streamSelector: '{foo="\\bar"}' }); + + expect(requestSpy).toHaveBeenCalledWith(expect.any(String), { + query: '%7Bfoo%3D%22%5Cbar%22%7D', + start: expect.any(Number), + end: expect.any(Number), + }); + }); }); }); diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index 9515571bfd8..9121fe11b9e 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -2,7 +2,7 @@ import { chain, difference } from 'lodash'; import { LRUCache } from 'lru-cache'; import Prism, { Grammar } from 'prismjs'; -import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery } from '@grafana/data'; +import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery, KeyValue } from '@grafana/data'; import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui'; import { extractLabelMatchers, @@ -452,18 +452,29 @@ export default class LokiLanguageProvider extends LanguageProvider { * It returns a promise that resolves to an array of strings containing the label values. * * @param labelName - The name of the label for which you want to retrieve values. + * @param options - (Optional) An object containing additional options - currently only stream selector. + * @param options.streamSelector - (Optional) The stream selector to filter label values. If not provided, all label values are fetched. * @returns A promise containing an array of label values. * @throws An error if the fetch operation fails. */ - async fetchLabelValues(labelName: string): Promise { - const interpolatedKey = encodeURIComponent(this.datasource.interpolateString(labelName)); + async fetchLabelValues(labelName: string, options?: { streamSelector?: string }): Promise { + const label = encodeURIComponent(this.datasource.interpolateString(labelName)); + const streamParam = options?.streamSelector + ? encodeURIComponent(this.datasource.interpolateString(options.streamSelector)) + : undefined; - const url = `label/${interpolatedKey}/values`; + const url = `label/${label}/values`; const rangeParams = this.datasource.getTimeRangeParams(); const { start, end } = rangeParams; + const params: KeyValue = { start, end }; + let paramCacheKey = label; - const cacheKey = this.generateCacheKey(url, start, end, interpolatedKey); - const params = { start, end }; + if (streamParam) { + params.query = streamParam; + paramCacheKey += streamParam; + } + + const cacheKey = this.generateCacheKey(url, start, end, paramCacheKey); let labelValues = this.labelsCache.get(cacheKey); if (!labelValues) { diff --git a/public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md b/public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md index c84b75d57b4..b21e6e792bb 100644 --- a/public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md +++ b/public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md @@ -58,13 +58,15 @@ The `datasource.languageProvider.fetchLabelValues()` method is designed for fetc * It returns a promise that resolves to an array of strings containing the label values. * * @param labelName - The name of the label for which you want to retrieve values. + * @param options - (Optional) An object containing additional options - currently only stream selector. + * @param options.streamSelector - (Optional) The stream selector to filter label values. If not provided, all label values are fetched. * @returns A promise containing an array of label values. * @throws An error if the fetch operation fails. */ -async function fetchLabelValues(labelName: string): Promise; +async function fetchLabelValues(labelName: string, options?: { streamSelector?: string }): Promise; /** - * Example usage: + * Example usage without stream selector: */ const labelName = 'job'; @@ -74,6 +76,19 @@ try { } catch (error) { console.error(`Error fetching label values: ${error.message}`); } + +/** + * Example usage with stream selector: + */ + +const labelName = 'job'; +const streamSelector = '{app="grafana"}'; +try { + const values = await fetchLabelValues(labelName, { streamSelector }); + console.log(values); +} catch (error) { + console.error(`Error fetching label values: ${error.message}`); +} ``` ### Fetching Loki labels for a specified selector