From 2165c9b3f000f59c9fbda80d2bfe3bc74cd6d9dc Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 18 Dec 2023 21:43:16 +0100 Subject: [PATCH] Loki: Add `lokiQueryHints` feature flag (#78953) * add `lokiQueryHints` feature flag * language provider tests * fix unwrap tests * add feature toggle --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 11 ++++++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ .../datasource/loki/LanguageProvider.test.ts | 15 +++++++++++++ .../datasource/loki/LanguageProvider.ts | 22 ++++++++++++------- .../components/LokiQueryBuilder.tsx | 5 ++++- .../components/UnwrapParamEditor.test.tsx | 19 ++++++++++++++++ .../components/UnwrapParamEditor.tsx | 3 ++- 10 files changed, 72 insertions(+), 10 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 909d83f3d5d..cfccd488800 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -53,6 +53,7 @@ Some features are enabled by default. You can disable these feature by setting t | `alertingInsights` | Show the new alerting insights landing page | Yes | | `cloudWatchWildCardDimensionValues` | Fetches dimension values from CloudWatch to correctly label wildcard dimensions | Yes | | `displayAnonymousStats` | Enables anonymous stats to be shown in the UI for Grafana | Yes | +| `lokiQueryHints` | Enables query hints for Loki | Yes | ## Preview feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ad614af6da5..8aa8ef51929 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -169,4 +169,5 @@ export interface FeatureToggles { regressionTransformation?: boolean; displayAnonymousStats?: boolean; alertStateHistoryAnnotationsFromLoki?: boolean; + lokiQueryHints?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5d263a38c00..95d213bb2ef 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1270,5 +1270,16 @@ var ( RequiresRestart: true, Created: time.Date(2023, time.November, 30, 12, 0, 0, 0, time.UTC), }, + { + // this is mainly used a a way to quickly disable query hints as a safe guard for our infrastructure + Name: "lokiQueryHints", + Description: "Enables query hints for Loki", + Stage: FeatureStageGeneralAvailability, + FrontendOnly: true, + Expression: "true", + Owner: grafanaObservabilityLogsSquad, + AllowSelfServe: false, + Created: time.Date(2023, time.December, 18, 12, 0, 0, 0, time.UTC), + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index be651ca048d..f9d710e6ef9 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -150,3 +150,4 @@ tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,fal regressionTransformation,privatePreview,@grafana/grafana-bi-squad,2023-11-24,false,false,false,true displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,false,true alertStateHistoryAnnotationsFromLoki,experimental,@grafana/alerting-squad,2023-11-30,false,false,true,false +lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 947a6621971..e386649422c 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -610,4 +610,8 @@ const ( // FlagAlertStateHistoryAnnotationsFromLoki // Enable using Loki as the source for panel annotations generated by alert rules FlagAlertStateHistoryAnnotationsFromLoki = "alertStateHistoryAnnotationsFromLoki" + + // FlagLokiQueryHints + // Enables query hints for Loki + FlagLokiQueryHints = "lokiQueryHints" ) diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index 1cd28b22b9b..d5c70b1ad22 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -1,4 +1,5 @@ import { AbstractLabelOperator, DataFrame, TimeRange, dateTime, getDefaultTimeRange } from '@grafana/data'; +import { config } from '@grafana/runtime'; import LanguageProvider from './LanguageProvider'; import { DEFAULT_MAX_LINES_SAMPLE, LokiDatasource } from './datasource'; @@ -328,6 +329,14 @@ describe('Query imports', () => { }); describe('getParserAndLabelKeys()', () => { + const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints; + beforeAll(() => { + config.featureToggles.lokiQueryHints = true; + }); + afterAll(() => { + config.featureToggles.lokiQueryHints = queryHintsFeatureToggle; + }); + let datasource: LokiDatasource, languageProvider: LanguageProvider; const extractLogParserFromDataFrameMock = jest.mocked(extractLogParserFromDataFrame); const extractedLabelKeys = ['extracted', 'label']; @@ -452,6 +461,12 @@ describe('Query imports', () => { mockTimeRange ); }); + it('does not call dataSample with feature toggle disabled', async () => { + config.featureToggles.lokiQueryHints = false; + jest.spyOn(datasource, 'getDataSamples'); + languageProvider.getParserAndLabelKeys('{place="luna"}', { timeRange: mockTimeRange }); + expect(datasource.getDataSamples).not.toHaveBeenCalled(); + }); }); }); diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index 8452ac1f3ce..29f3fb8a7ee 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -2,6 +2,7 @@ import { LRUCache } from 'lru-cache'; import Prism from 'prismjs'; import { LanguageProvider, AbstractQuery, KeyValue, getDefaultTimeRange, TimeRange } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { extractLabelMatchers, processLabels, toPromLikeExpr } from 'app/plugins/datasource/prometheus/language_utils'; import { DEFAULT_MAX_LINES_SAMPLE, LokiDatasource } from './datasource'; @@ -261,6 +262,18 @@ export default class LokiLanguageProvider extends LanguageProvider { streamSelector: string, options?: { maxLines?: number; timeRange?: TimeRange } ): Promise { + const empty = { + extractedLabelKeys: [], + structuredMetadataKeys: [], + unwrapLabelKeys: [], + hasJSON: false, + hasLogfmt: false, + hasPack: false, + }; + if (!config.featureToggles.lokiQueryHints) { + return empty; + } + const series = await this.datasource.getDataSamples( { expr: streamSelector, @@ -271,14 +284,7 @@ export default class LokiLanguageProvider extends LanguageProvider { ); if (!series.length) { - return { - extractedLabelKeys: [], - structuredMetadataKeys: [], - unwrapLabelKeys: [], - hasJSON: false, - hasLogfmt: false, - hasPack: false, - }; + return empty; } const { hasLogfmt, hasJSON, hasPack } = extractLogParserFromDataFrame(series[0]); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx index 6e6612541d0..6cea7c08267 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { DataSourceApi, getDefaultTimeRange, LoadingState, PanelData, SelectableValue, TimeRange } from '@grafana/data'; import { EditorRow } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters'; import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox'; import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList'; @@ -105,7 +106,9 @@ export const LokiQueryBuilder = React.memo( setSampleData(sampleData); }; - onGetSampleData().catch(console.error); + if (config.featureToggles.lokiQueryHints) { + onGetSampleData().catch(console.error); + } }, [datasource, query, timeRange]); const lang = { grammar: logqlGrammar, name: 'logql' }; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx index c10e27603b4..cb43f4c3089 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx @@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event'; import React, { ComponentProps } from 'react'; import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { QueryBuilderOperation, QueryBuilderOperationParamDef, @@ -15,6 +16,14 @@ import { LokiOperationId } from '../types'; import { UnwrapParamEditor } from './UnwrapParamEditor'; describe('UnwrapParamEditor', () => { + const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints; + beforeAll(() => { + config.featureToggles.lokiQueryHints = true; + }); + afterAll(() => { + config.featureToggles.lokiQueryHints = queryHintsFeatureToggle; + }); + it('shows value if value present', () => { const props = createProps({ value: 'unique' }); render(); @@ -53,6 +62,16 @@ describe('UnwrapParamEditor', () => { expect(await screen.findByText('status')).toBeInTheDocument(); expect(await screen.findByText('duration')).toBeInTheDocument(); }); + + it('does not show labels with unwrap-friendly values when feature is disabled', async () => { + config.featureToggles.lokiQueryHints = false; + const props = createProps({}, frames); + render(); + const input = screen.getByRole('combobox'); + await userEvent.click(input); + expect(screen.queryByText('status')).not.toBeInTheDocument(); + expect(screen.queryByText('duration')).not.toBeInTheDocument(); + }); }); const createProps = ( diff --git a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx index 40a9f5899da..f013a162fad 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { Select } from '@grafana/ui'; import { QueryBuilderOperationParamEditorProps } from '../../../prometheus/querybuilder/shared/types'; @@ -31,7 +32,7 @@ export function UnwrapParamEditor({ inputId={getOperationParamId(operationId, index)} onOpenMenu={async () => { // This check is always true, we do it to make typescript happy - if (datasource instanceof LokiDatasource) { + if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) { setState({ isLoading: true }); const options = await loadUnwrapOptions(query, datasource, timeRange); setState({ options, isLoading: undefined });