From 5ac7443fcec0db412d3333044a82c2c26b5aece7 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Fri, 13 Dec 2024 15:31:41 +0100 Subject: [PATCH] Loki: Default to `/labels` API with `query` param instead of `/series` API (#97935) * feat(loki-labels-api): add feature toggle * feat(loki-labels-api): default to `/labels` API --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 7 +++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/featuremgmt/toggles_gen.json | 13 +++++ .../datasource/loki/LanguageProvider.test.ts | 54 +++++++++++++++---- .../datasource/loki/LanguageProvider.ts | 20 ++++--- 8 files changed, 84 insertions(+), 17 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 6d5fc19c45d..98aae495e29 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -84,6 +84,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `zipkinBackendMigration` | Enables querying Zipkin data source without the proxy | Yes | | `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes | | `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes | +| `lokiLabelNamesQueryApi` | Defaults to using the Loki `/labels` API instead of `/series` | Yes | ## Public preview feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index e1674056da5..927402e7cfe 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -244,4 +244,5 @@ export interface FeatureToggles { feedbackButton?: boolean; elasticsearchCrossClusterSearch?: boolean; unifiedHistory?: boolean; + lokiLabelNamesQueryApi?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index da25af3ad8e..ea72a4927fa 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1689,6 +1689,13 @@ var ( Owner: grafanaFrontendPlatformSquad, FrontendOnly: true, }, + { + Name: "lokiLabelNamesQueryApi", + Description: "Defaults to using the Loki `/labels` API instead of `/series`", + Stage: FeatureStageGeneralAvailability, + Owner: grafanaObservabilityLogsSquad, + Expression: "true", + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 90ae1baaee2..90e7cea0120 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -225,3 +225,4 @@ alertingNotificationsStepMode,experimental,@grafana/alerting-squad,false,false,t feedbackButton,experimental,@grafana/grafana-operator-experience-squad,false,false,false elasticsearchCrossClusterSearch,preview,@grafana/aws-datasources,false,false,false unifiedHistory,experimental,@grafana/grafana-frontend-platform,false,false,true +lokiLabelNamesQueryApi,GA,@grafana/observability-logs,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 963de702c4e..ee44576488f 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -910,4 +910,8 @@ const ( // FlagUnifiedHistory // Displays the navigation history so the user can navigate back to previous pages FlagUnifiedHistory = "unifiedHistory" + + // FlagLokiLabelNamesQueryApi + // Defaults to using the Loki `/labels` API instead of `/series` + FlagLokiLabelNamesQueryApi = "lokiLabelNamesQueryApi" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 1371db06c74..875816b2b03 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2095,6 +2095,19 @@ "codeowner": "@grafana/observability-logs" } }, + { + "metadata": { + "name": "lokiLabelNamesQueryApi", + "resourceVersion": "1734096677730", + "creationTimestamp": "2024-12-13T13:31:17Z" + }, + "spec": { + "description": "Defaults to using the Loki `/labels` API instead of `/series`", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "expression": "true" + } + }, { "metadata": { "name": "lokiLogsDataplane", diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index c801a15c7a5..8f51a07d586 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -428,17 +428,51 @@ describe('Language completion provider', () => { expect(instance.request).toHaveBeenCalledWith('labels', datasourceWithLabels.getTimeRangeParams(mockTimeRange)); }); - it('should use series endpoint for request with stream selector', async () => { - const datasourceWithLabels = setup({}); - datasourceWithLabels.languageProvider.request = jest.fn(); + describe('without labelNames feature toggle', () => { + const lokiLabelNamesQueryApi = config.featureToggles.lokiLabelNamesQueryApi; + beforeAll(() => { + config.featureToggles.lokiLabelNamesQueryApi = false; + }); + afterAll(() => { + config.featureToggles.lokiLabelNamesQueryApi = lokiLabelNamesQueryApi; + }); - const instance = new LanguageProvider(datasourceWithLabels); - instance.request = jest.fn(); - await instance.fetchLabels({ streamSelector: '{foo="bar"}' }); - expect(instance.request).toHaveBeenCalledWith('series', { - end: 1560163909000, - 'match[]': '{foo="bar"}', - start: 1560153109000, + it('should use series endpoint for request with stream selector', async () => { + const datasourceWithLabels = setup({}); + datasourceWithLabels.languageProvider.request = jest.fn(); + + const instance = new LanguageProvider(datasourceWithLabels); + instance.request = jest.fn(); + await instance.fetchLabels({ streamSelector: '{foo="bar"}' }); + expect(instance.request).toHaveBeenCalledWith('series', { + end: 1560163909000, + 'match[]': '{foo="bar"}', + start: 1560153109000, + }); + }); + }); + + describe('with labelNames feature toggle', () => { + const lokiLabelNamesQueryApi = config.featureToggles.lokiLabelNamesQueryApi; + beforeAll(() => { + config.featureToggles.lokiLabelNamesQueryApi = true; + }); + afterAll(() => { + config.featureToggles.lokiLabelNamesQueryApi = lokiLabelNamesQueryApi; + }); + + it('should use series endpoint for request with stream selector', async () => { + const datasourceWithLabels = setup({}); + datasourceWithLabels.languageProvider.request = jest.fn(); + + const instance = new LanguageProvider(datasourceWithLabels); + instance.request = jest.fn(); + await instance.fetchLabels({ streamSelector: '{foo="bar"}' }); + expect(instance.request).toHaveBeenCalledWith('labels', { + end: 1560163909000, + query: '{foo="bar"}', + start: 1560153109000, + }); }); }); diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index 5d6e5684f2b..d3b4f999608 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -147,8 +147,8 @@ export default class LokiLanguageProvider extends LanguageProvider { * @throws An error if the fetch operation fails. */ async fetchLabels(options?: { streamSelector?: string; timeRange?: TimeRange }): Promise { - // If there is no stream selector - use /labels endpoint (https://github.com/grafana/loki/pull/11982) - if (!options || !options.streamSelector) { + // We'll default to use `/labels`. If the flag is disabled, and there's a streamSelector, we'll use the series endpoint. + if (config.featureToggles.lokiLabelNamesQueryApi || !options?.streamSelector) { return this.fetchLabelsByLabelsEndpoint(options); } else { const data = await this.fetchSeriesLabels(options.streamSelector, { timeRange: options.timeRange }); @@ -166,14 +166,20 @@ export default class LokiLanguageProvider extends LanguageProvider { * @returns A promise containing an array of label keys. * @throws An error if the fetch operation fails. */ - private async fetchLabelsByLabelsEndpoint(options?: { timeRange?: TimeRange }): Promise { + private async fetchLabelsByLabelsEndpoint(options?: { + streamSelector?: string; + timeRange?: TimeRange; + }): Promise { const url = 'labels'; const range = options?.timeRange ?? this.getDefaultTimeRange(); - const timeRange = this.datasource.getTimeRangeParams(range); - - const res = await this.request(url, timeRange); + const { start, end } = this.datasource.getTimeRangeParams(range); + const params: Record = { start, end }; + if (options?.streamSelector) { + params['query'] = options.streamSelector; + } + const res = await this.request(url, params); if (Array.isArray(res)) { - const labels = res + const labels = Array.from(new Set(res)) .slice() .sort() .filter((label: string) => label.startsWith('__') === false);