From c49caead2566dbf12f0cbd23d3725f382b8dfb14 Mon Sep 17 00:00:00 2001
From: Sam Jewell <2903904+samjewell@users.noreply.github.com>
Date: Mon, 10 Nov 2025 17:17:49 +0000
Subject: [PATCH] [--Dashboard-- data source] AdHoc filtering: Remove feature
toggle (#113674)
* Remove dashboardDsAdHocFiltering feature toggle
The dashboardDsAdHocFiltering feature toggle has been enabled by default
and is now in General Availability stage. This commit removes the feature
toggle and makes the AdHoc filtering functionality for the dashboard
datasource permanently available.
Changes:
- Remove feature toggle from registry.go
- Regenerate feature toggle files
- Remove conditional checks in frontend code
- Update tests to reflect permanent enablement
- Always show AdHoc Filters toggle in dashboard query editor
- Always enable dashboard datasource in DataSourcePicker for variables
* Remove unused imports
* Fix Prettier formatting issues
---
.../feature-toggles/index.md | 1 -
.../src/types/featureToggles.gen.ts | 5 --
pkg/services/featuremgmt/registry.go | 8 ---
pkg/services/featuremgmt/toggles_gen.csv | 1 -
pkg/services/featuremgmt/toggles_gen.go | 4 --
pkg/services/featuremgmt/toggles_gen.json | 1 +
.../components/AdHocVariableForm.tsx | 3 +-
.../dashboard/DashboardQueryEditor.test.tsx | 37 +---------
.../dashboard/DashboardQueryEditor.tsx | 16 ++---
.../datasource/dashboard/datasource.test.ts | 71 +------------------
.../datasource/dashboard/datasource.ts | 24 +++----
11 files changed, 20 insertions(+), 151 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 dea03f07484..2653da9f879 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -79,7 +79,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `grafanaAssistantInProfilesDrilldown` | Enables integration with Grafana Assistant in Profiles Drilldown | Yes |
| `sharingDashboardImage` | Enables image sharing functionality for dashboards | Yes |
| `tabularNumbers` | Use fixed-width numbers globally in the UI | |
-| `dashboardDsAdHocFiltering` | Enables adhoc filtering support for the dashboard datasource | Yes |
| `adhocFiltersInTooltips` | Enable adhoc filter buttons in visualization tooltips | Yes |
| `tempoSearchBackendMigration` | Run search queries through the tempo backend | |
diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts
index 89e1f8e9aed..d77c6821339 100644
--- a/packages/grafana-data/src/types/featureToggles.gen.ts
+++ b/packages/grafana-data/src/types/featureToggles.gen.ts
@@ -1119,11 +1119,6 @@ export interface FeatureToggles {
*/
unifiedStorageSearchDualReaderEnabled?: boolean;
/**
- * Enables adhoc filtering support for the dashboard datasource
- * @default true
- */
- dashboardDsAdHocFiltering?: boolean;
- /**
* Supports __from and __to macros that always use the dashboard level time range
*/
dashboardLevelTimeMacros?: boolean;
diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go
index 6bc730b8d50..0b4f483e502 100644
--- a/pkg/services/featuremgmt/registry.go
+++ b/pkg/services/featuremgmt/registry.go
@@ -1942,14 +1942,6 @@ var (
HideFromAdminPage: true,
HideFromDocs: true,
},
- {
- Name: "dashboardDsAdHocFiltering",
- Description: "Enables adhoc filtering support for the dashboard datasource",
- Stage: FeatureStageGeneralAvailability,
- Owner: grafanaDataProSquad,
- FrontendOnly: true,
- Expression: "true",
- },
{
Name: "dashboardLevelTimeMacros",
Description: "Supports __from and __to macros that always use the dashboard level time range",
diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv
index 8695ec64f57..3822dfc8c8d 100644
--- a/pkg/services/featuremgmt/toggles_gen.csv
+++ b/pkg/services/featuremgmt/toggles_gen.csv
@@ -250,7 +250,6 @@ foldersAppPlatformAPI,experimental,@grafana/grafana-search-navigate-organise,fal
otelLogsFormatting,experimental,@grafana/observability-logs,false,false,true
alertingNotificationHistory,experimental,@grafana/alerting-squad,false,false,false
unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,false,false,false
-dashboardDsAdHocFiltering,GA,@grafana/datapro,false,false,true
dashboardLevelTimeMacros,experimental,@grafana/dashboards-squad,false,false,true
alertmanagerRemoteSecondaryWithRemoteState,experimental,@grafana/alerting-squad,false,false,false
restrictedPluginApis,experimental,@grafana/plugins-platform-backend,false,false,true
diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go
index fe407b9066f..a1a1be66f54 100644
--- a/pkg/services/featuremgmt/toggles_gen.go
+++ b/pkg/services/featuremgmt/toggles_gen.go
@@ -1010,10 +1010,6 @@ const (
// Enable dual reader for unified storage search
FlagUnifiedStorageSearchDualReaderEnabled = "unifiedStorageSearchDualReaderEnabled"
- // FlagDashboardDsAdHocFiltering
- // Enables adhoc filtering support for the dashboard datasource
- FlagDashboardDsAdHocFiltering = "dashboardDsAdHocFiltering"
-
// FlagDashboardLevelTimeMacros
// Supports __from and __to macros that always use the dashboard level time range
FlagDashboardLevelTimeMacros = "dashboardLevelTimeMacros"
diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json
index dbf4d982629..05c83353ac6 100644
--- a/pkg/services/featuremgmt/toggles_gen.json
+++ b/pkg/services/featuremgmt/toggles_gen.json
@@ -1061,6 +1061,7 @@
"name": "dashboardDsAdHocFiltering",
"resourceVersion": "1756814786992",
"creationTimestamp": "2025-07-23T08:12:25Z",
+ "deletionTimestamp": "2025-09-27T19:59:33Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-09-02 12:06:26.992384 +0000 UTC"
}
diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx
index 29183c79531..5434068f21e 100644
--- a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx
+++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx
@@ -4,7 +4,6 @@ import { DataSourceInstanceSettings, MetricFindValue, readCSV } from '@grafana/d
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { EditorField } from '@grafana/plugin-ui';
-import { config } from '@grafana/runtime';
import { DataSourceRef } from '@grafana/schema';
import { Alert, CodeEditor, Field, Switch, Box } from '@grafana/ui';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
@@ -67,7 +66,7 @@ export function AdHocVariableForm({
onChange={onDataSourceChange}
width={30}
variables={true}
- dashboard={config.featureToggles.dashboardDsAdHocFiltering}
+ dashboard={true}
noDefault
/>
diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx
index 2cbfa311d7b..876e996dbe8 100644
--- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx
+++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx
@@ -1,8 +1,7 @@
-import { act, render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
-import config from 'app/core/config';
import { mockDataSource } from 'app/features/alerting/unified/mocks';
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
@@ -30,9 +29,6 @@ jest.mock('app/core/config', () => ({
},
},
},
- featureToggles: {
- dashboardDsAdHocFiltering: false, // Default to false, can be overridden in tests
- },
}));
setupDataSources(mockDataSource({ isDefault: true }));
@@ -179,11 +175,7 @@ describe('DashboardQueryEditor', () => {
jest.spyOn(getDashboardSrv(), 'getCurrent').mockImplementation(() => mockDashboard);
});
- it('shows the AdHoc Filters toggle when feature toggle is enabled', async () => {
- await act(async () => {
- config.featureToggles.dashboardDsAdHocFiltering = true;
- });
-
+ it('shows the AdHoc Filters toggle', async () => {
const query: DashboardQuery = { refId: 'A', panelId: 1, adHocFiltersEnabled: false };
await act(async () => {
@@ -201,30 +193,5 @@ describe('DashboardQueryEditor', () => {
const adhocFiltersToggle = await screen.findByText('AdHoc Filters');
expect(adhocFiltersToggle).toBeInTheDocument();
});
-
- it('does not show the AdHoc Filters toggle when feature toggle is disabled', async () => {
- await act(async () => {
- config.featureToggles.dashboardDsAdHocFiltering = false;
- });
-
- const query: DashboardQuery = { refId: 'A', panelId: 1, adHocFiltersEnabled: false };
-
- await act(async () => {
- render(
-
- );
- });
-
- // Wait for any async operations to complete
- await waitFor(() => {
- expect(screen.queryByText('AdHoc Filters')).not.toBeInTheDocument();
- });
- });
});
});
diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
index e137069219d..d8e9b952281 100644
--- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
+++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
@@ -205,15 +205,13 @@ export function DashboardQueryEditor({ data, query, onChange, onRunQuery }: Prop
)}
- {config.featureToggles.dashboardDsAdHocFiltering && (
-
-
-
- )}
+
+
+
{loadingResults ? (
diff --git a/public/app/plugins/datasource/dashboard/datasource.test.ts b/public/app/plugins/datasource/dashboard/datasource.test.ts
index 55d7a12968d..2dcb17c63db 100644
--- a/public/app/plugins/datasource/dashboard/datasource.test.ts
+++ b/public/app/plugins/datasource/dashboard/datasource.test.ts
@@ -12,7 +12,7 @@ import {
AdHocVariableFilter,
} from '@grafana/data';
import { getPanelPlugin } from '@grafana/data/test';
-import { setPluginImportUtils, config } from '@grafana/runtime';
+import { setPluginImportUtils } from '@grafana/runtime';
import {
SafeSerializableSceneObject,
SceneDataNode,
@@ -178,16 +178,6 @@ describe('DashboardDatasource', () => {
// Test AdHoc filtering via the Public API first, to ensure Integration
describe('Integration (Public API)', () => {
- const originalToggleValue = config.featureToggles.dashboardDsAdHocFiltering;
-
- beforeEach(() => {
- config.featureToggles.dashboardDsAdHocFiltering = true;
- });
-
- afterEach(() => {
- config.featureToggles.dashboardDsAdHocFiltering = originalToggleValue;
- });
-
it('should apply basic filtering end-to-end through public query method', async () => {
const testFrame = createTestFrame([
{ name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] },
@@ -224,46 +214,6 @@ describe('DashboardDatasource', () => {
expect(result?.data[0].length).toBe(1);
});
- it('should respect feature toggle and not filter when disabled', async () => {
- // Temporarily disable the feature toggle for this test
- config.featureToggles.dashboardDsAdHocFiltering = false;
-
- const testFrame = createTestFrame([
- { name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] },
- { name: 'age', type: FieldType.number, values: [25, 30, 35] },
- ]);
-
- const scene = new SceneFlexLayout({
- children: [
- new SceneFlexItem({
- body: new VizPanel({
- key: getVizPanelKeyForPanelId(1),
- $data: new SceneDataNode({
- data: {
- series: [testFrame],
- state: LoadingState.Done,
- timeRange: getDefaultTimeRange(),
- },
- }),
- }),
- }),
- ],
- });
-
- const ds = new DashboardDatasource({} as DataSourceInstanceSettings);
- const filters: AdHocVariableFilter[] = [{ key: 'name', operator: '=', value: 'John' }];
-
- const observable = ds.query(createQueryRequest(filters, scene));
-
- let result: DataQueryResponse | undefined;
- observable.subscribe({ next: (data) => (result = data) });
-
- // Should return unfiltered data since feature toggle is disabled
- expect(result?.data[0].fields[0].values).toEqual(['John', 'Jane', 'Bob']);
- expect(result?.data[0].fields[1].values).toEqual([25, 30, 35]);
- expect(result?.data[0].length).toBe(3);
- });
-
it('should respect per-panel adHocFiltersEnabled setting and not filter when disabled', async () => {
const testFrame = createTestFrame([
{ name: 'name', type: FieldType.string, values: ['John', 'Jane', 'Bob'] },
@@ -723,27 +673,8 @@ describe('DashboardDatasource', () => {
});
describe('getDrilldownsApplicability', () => {
- const originalToggleValue = config.featureToggles.dashboardDsAdHocFiltering;
const ds = new DashboardDatasource({} as DataSourceInstanceSettings);
- beforeEach(() => {
- config.featureToggles.dashboardDsAdHocFiltering = true;
- });
-
- afterEach(() => {
- config.featureToggles.dashboardDsAdHocFiltering = originalToggleValue;
- });
-
- it('should return empty array when feature toggle is disabled', async () => {
- config.featureToggles.dashboardDsAdHocFiltering = false;
-
- const result = await ds.getDrilldownsApplicability({
- filters: [{ key: 'name', operator: '=', value: 'test' }],
- });
-
- expect(result).toEqual([]);
- });
-
it('should mark supported operators as applicable', async () => {
const result = await ds.getDrilldownsApplicability({
filters: [
diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts
index 9969c9afe7f..34a1781623f 100644
--- a/public/app/plugins/datasource/dashboard/datasource.ts
+++ b/public/app/plugins/datasource/dashboard/datasource.ts
@@ -20,7 +20,6 @@ import {
DataSourceGetDrilldownsApplicabilityOptions,
DrilldownsApplicability,
} from '@grafana/data';
-import { config } from '@grafana/runtime';
import { isSceneObject, SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes';
import {
activateSceneObjectAndParentTree,
@@ -140,11 +139,10 @@ export class DashboardDatasource extends DataSourceApi {
...field,
config: {
...field.config,
- // Enable AdHoc filtering for string and numeric fields only when feature toggle and per-panel setting are enabled
- filterable:
- config.featureToggles.dashboardDsAdHocFiltering && query.adHocFiltersEnabled
- ? field.type === FieldType.string || field.type === FieldType.number
- : field.config.filterable,
+ // Enable AdHoc filtering for string and numeric fields only when per-panel setting is enabled
+ filterable: query.adHocFiltersEnabled
+ ? field.type === FieldType.string || field.type === FieldType.number
+ : field.config.filterable,
},
state: {
...field.state,
@@ -153,7 +151,7 @@ export class DashboardDatasource extends DataSourceApi {
};
});
- if (!config.featureToggles.dashboardDsAdHocFiltering || !query.adHocFiltersEnabled || filters.length === 0) {
+ if (!query.adHocFiltersEnabled || filters.length === 0) {
return [...series, ...annotations];
}
@@ -247,11 +245,9 @@ export class DashboardDatasource extends DataSourceApi {
const field = frame.fields[fieldIndex];
- // Only support string and numeric fields when feature toggle is enabled
- if (config.featureToggles.dashboardDsAdHocFiltering) {
- if (field.type !== FieldType.string && field.type !== FieldType.number) {
- return null;
- }
+ // Only support string and numeric fields
+ if (field.type !== FieldType.string && field.type !== FieldType.number) {
+ return null;
}
// Map operator to matcher ID
@@ -357,10 +353,6 @@ export class DashboardDatasource extends DataSourceApi {
async getDrilldownsApplicability(
options?: DataSourceGetDrilldownsApplicabilityOptions
): Promise {
- if (!config.featureToggles.dashboardDsAdHocFiltering) {
- return [];
- }
-
// Check if any query has adhoc filters enabled
const hasAdHocFiltersEnabled = options?.queries?.some((query) => query.adHocFiltersEnabled);