From 5cd3ad76ee4ff309aa1ae9b98ee1d65c6c55398b Mon Sep 17 00:00:00 2001
From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
Date: Wed, 2 Jul 2025 12:08:22 +0200
Subject: [PATCH] Loki: Remove `lokiQueryHints` feature toggle (#106620)
* Loki: Remove lokiQueryHints feature toggle
* Remove unused imports
* Fix tests
---
.../feature-toggles/index.md | 2 +-
.../src/types/featureToggles.gen.ts | 5 ++---
pkg/services/featuremgmt/registry.go | 13 +++++------
pkg/services/featuremgmt/toggles_gen.csv | 2 +-
pkg/services/featuremgmt/toggles_gen.go | 6 ++---
pkg/services/featuremgmt/toggles_gen.json | 5 +++--
.../datasource/loki/LanguageProvider.test.ts | 14 ------------
.../datasource/loki/LanguageProvider.ts | 3 ---
.../components/LabelParamEditor.test.tsx | 9 --------
.../components/LokiQueryBuilder.test.tsx | 9 --------
.../components/LokiQueryBuilder.tsx | 3 +--
.../LokiQueryBuilderContainer.test.tsx | 22 ++++++++++++++-----
.../components/UnwrapParamEditor.test.tsx | 19 ----------------
.../components/UnwrapParamEditor.tsx | 3 +--
14 files changed, 33 insertions(+), 82 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 da44ded91fe..ef2ca7ab494 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -53,7 +53,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
-| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate various on-prem resources to your Grafana Cloud stack. | Yes |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
@@ -103,6 +102,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
+| `regressionTransformation` | Enables regression analysis transformation |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage |
| `tableNextGen` | Allows access to the new react-data-grid based table component. |
| `enableSCIM` | Enables SCIM support for user and group management |
diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts
index e76e1b494ae..167962c0745 100644
--- a/packages/grafana-data/src/types/featureToggles.gen.ts
+++ b/packages/grafana-data/src/types/featureToggles.gen.ts
@@ -420,10 +420,9 @@ export interface FeatureToggles {
*/
tableSharedCrosshair?: boolean;
/**
- * Enables query hints for Loki
- * @default true
+ * Enables regression analysis transformation
*/
- lokiQueryHints?: boolean;
+ regressionTransformation?: boolean;
/**
* Use the kubernetes API for feature toggle management in the frontend
*/
diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go
index 417d95f0d52..fe6fe7d4fab 100644
--- a/pkg/services/featuremgmt/registry.go
+++ b/pkg/services/featuremgmt/registry.go
@@ -698,14 +698,11 @@ var (
Owner: grafanaDatavizSquad,
},
{
- // this is mainly used as a way to quickly disable query hints as a safeguard for our infrastructure
- Name: "lokiQueryHints",
- Description: "Enables query hints for Loki",
- Stage: FeatureStageGeneralAvailability,
- FrontendOnly: true,
- Expression: "true",
- Owner: grafanaObservabilityLogsSquad,
- AllowSelfServe: false,
+ Name: "regressionTransformation",
+ Description: "Enables regression analysis transformation",
+ Stage: FeatureStagePublicPreview,
+ FrontendOnly: true,
+ Owner: grafanaDatavizSquad,
},
{
Name: "kubernetesFeatureToggles",
diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv
index bbd8023623b..5b44906dfc1 100644
--- a/pkg/services/featuremgmt/toggles_gen.csv
+++ b/pkg/services/featuremgmt/toggles_gen.csv
@@ -92,7 +92,7 @@ logsInfiniteScrolling,GA,@grafana/observability-logs,false,false,true
logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true
pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false
tableSharedCrosshair,experimental,@grafana/dataviz-squad,false,false,true
-lokiQueryHints,GA,@grafana/observability-logs,false,false,true
+regressionTransformation,preview,@grafana/dataviz-squad,false,false,true
kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,false,false,true
cloudRBACRoles,preview,@grafana/identity-access-team,false,true,false
alertingQueryOptimization,GA,@grafana/alerting-squad,false,false,false
diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go
index 47d4562df37..bc513ac82d5 100644
--- a/pkg/services/featuremgmt/toggles_gen.go
+++ b/pkg/services/featuremgmt/toggles_gen.go
@@ -379,9 +379,9 @@ const (
// Enables shared crosshair in table panel
FlagTableSharedCrosshair = "tableSharedCrosshair"
- // FlagLokiQueryHints
- // Enables query hints for Loki
- FlagLokiQueryHints = "lokiQueryHints"
+ // FlagRegressionTransformation
+ // Enables regression analysis transformation
+ FlagRegressionTransformation = "regressionTransformation"
// FlagKubernetesFeatureToggles
// Use the kubernetes API for feature toggle management in the frontend
diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json
index ce8ab6502f3..c1cb552b6bb 100644
--- a/pkg/services/featuremgmt/toggles_gen.json
+++ b/pkg/services/featuremgmt/toggles_gen.json
@@ -1868,8 +1868,9 @@
{
"metadata": {
"name": "lokiQueryHints",
- "resourceVersion": "1750434297879",
- "creationTimestamp": "2023-12-18T20:43:16Z"
+ "resourceVersion": "1743693517832",
+ "creationTimestamp": "2023-12-18T20:43:16Z",
+ "deletionTimestamp": "2025-06-12T12:14:47Z"
},
"spec": {
"description": "Enables query hints for Loki",
diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts
index dec28df9f0a..eb449333632 100644
--- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts
+++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts
@@ -681,14 +681,6 @@ 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'];
@@ -810,12 +802,6 @@ 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 d6bae1ea6d1..5607980b557 100644
--- a/public/app/plugins/datasource/loki/LanguageProvider.ts
+++ b/public/app/plugins/datasource/loki/LanguageProvider.ts
@@ -477,9 +477,6 @@ export default class LokiLanguageProvider extends LanguageProvider {
hasLogfmt: false,
hasPack: false,
};
- if (!config.featureToggles.lokiQueryHints) {
- return empty;
- }
const series = await this.datasource.getDataSamples(
{
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx
index 22dd10dda7a..6439936934d 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx
@@ -4,7 +4,6 @@ import { ComponentProps } from 'react';
import { DataSourceApi } from '@grafana/data';
import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/plugin-ui';
-import { config } from '@grafana/runtime';
import { LokiDatasource } from '../../datasource';
import { createLokiDatasource } from '../../mocks/datasource';
@@ -14,14 +13,6 @@ import { LokiOperationId } from '../types';
import { LabelParamEditor } from './LabelParamEditor';
describe('LabelParamEditor', () => {
- const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints;
- beforeAll(() => {
- config.featureToggles.lokiQueryHints = true;
- });
- afterAll(() => {
- config.featureToggles.lokiQueryHints = queryHintsFeatureToggle;
- });
-
it('shows label options', async () => {
const props = createProps({}, ['label1', 'label2']);
render();
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx
index 2d3d1a73461..dc6dcbce805 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx
@@ -2,7 +2,6 @@ import { render, screen, getAllByRole, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { dateTime } from '@grafana/data';
-import { config } from '@grafana/runtime';
import { createLokiDatasource } from '../../mocks/datasource';
import { LokiOperationId, LokiVisualQuery } from '../types';
@@ -40,14 +39,6 @@ const createDefaultProps = () => {
};
describe('LokiQueryBuilder', () => {
- const originalLokiQueryHints = config.featureToggles.lokiQueryHints;
- beforeEach(() => {
- config.featureToggles.lokiQueryHints = true;
- });
-
- afterEach(() => {
- config.featureToggles.lokiQueryHints = originalLokiQueryHints;
- });
it('tries to load label names', async () => {
const props = createDefaultProps();
props.datasource.getDataSamples = jest.fn().mockResolvedValue([]);
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx
index 96e7a1fa79c..632ac1f7a11 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx
@@ -15,7 +15,6 @@ import {
QueryBuilderLabelFilter,
QueryBuilderOperation,
} from '@grafana/plugin-ui';
-import { config } from '@grafana/runtime';
import { testIds } from '../../components/LokiQueryEditor';
import { LokiDatasource } from '../../datasource';
@@ -126,7 +125,7 @@ export const LokiQueryBuilder = memo(({ datasource, query, onChange, onRu
(Math.abs(timeRange.to.valueOf() - prevTimeRange.to.valueOf()) > TIME_SPAN_TO_TRIGGER_SAMPLES ||
Math.abs(timeRange.from.valueOf() - prevTimeRange.from.valueOf()) > TIME_SPAN_TO_TRIGGER_SAMPLES);
const updateBasedOnChangedQuery = !isEqual(prevQuery, query);
- if (config.featureToggles.lokiQueryHints && (updateBasedOnChangedTimeRange || updateBasedOnChangedQuery)) {
+ if (updateBasedOnChangedTimeRange || updateBasedOnChangedQuery) {
onGetSampleData().catch(console.error);
}
}, [datasource, query, timeRange, prevQuery, prevTimeRange]);
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx
index 3c8c5c94704..ac1ffe809c0 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen, waitFor, findAllByRole } from '@testing-library/react';
+import { render, screen, waitFor, findAllByRole, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createLokiDatasource } from '../../mocks/datasource';
@@ -19,7 +19,9 @@ describe('LokiQueryBuilderContainer', () => {
};
props.datasource.getDataSamples = jest.fn().mockResolvedValue([]);
- render();
+ await act(async () => {
+ render();
+ });
const selector = await screen.findByLabelText('selector');
expect(selector.textContent).toBe('{job="testjob"}');
await addOperation('Range functions', 'Rate');
@@ -45,7 +47,9 @@ describe('LokiQueryBuilderContainer', () => {
props.datasource.languageProvider.fetchLabelValues = jest.fn().mockReturnValue(['grafana', 'loki']);
props.onChange = jest.fn();
- render();
+ await act(async () => {
+ render();
+ });
await userEvent.click(screen.getByLabelText('Add'));
const labels = screen.getByText(/Label filters/);
const selects = await findAllByRole(getSelectParent(labels)!, 'combobox');
@@ -78,7 +82,9 @@ describe('LokiQueryBuilderContainer', () => {
showExplain: false,
};
props.datasource.getDataSamples = jest.fn().mockResolvedValue([]);
- render();
+ await act(async () => {
+ render();
+ });
expect(screen.getByText('{')).toHaveClass('token punctuation');
expect(screen.getByText('"baz"')).toHaveClass('token label-value attr-value');
expect(screen.getByText('|')).toHaveClass('token pipe-operator operator');
@@ -98,7 +104,9 @@ describe('LokiQueryBuilderContainer', () => {
};
props.datasource.getDataSamples = jest.fn().mockResolvedValue([]);
- render();
+ await act(async () => {
+ render();
+ });
expect(screen.getAllByText('You have conflicting label filters')).toHaveLength(2);
});
@@ -115,7 +123,9 @@ describe('LokiQueryBuilderContainer', () => {
};
props.datasource.getDataSamples = jest.fn().mockResolvedValue([]);
- render();
+ await act(async () => {
+ render();
+ });
expect(screen.getByText('<')).toBeInTheDocument();
expect(screen.getByText('expr')).toBeInTheDocument();
expect(screen.getByText('>')).toBeInTheDocument();
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 beacb3736b4..f21d37b2544 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx
@@ -4,7 +4,6 @@ import { ComponentProps } from 'react';
import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/plugin-ui';
-import { config } from '@grafana/runtime';
import { LokiDatasource } from '../../datasource';
import { createLokiDatasource } from '../../mocks/datasource';
@@ -14,14 +13,6 @@ 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();
@@ -60,16 +51,6 @@ 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 630cb285568..bfb8306b6f0 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx
@@ -2,7 +2,6 @@ import { useState } from 'react';
import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
import { QueryBuilderOperationParamEditorProps, VisualQueryModeller } from '@grafana/plugin-ui';
-import { config } from '@grafana/runtime';
import { Select } from '@grafana/ui';
import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation';
@@ -32,7 +31,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 && config.featureToggles.lokiQueryHints) {
+ if (datasource instanceof LokiDatasource) {
setState({ isLoading: true });
const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange);
setState({ options, isLoading: undefined });