[--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
This commit is contained in:
Sam Jewell
2025-11-10 17:17:49 +00:00
committed by GitHub
parent ac9259d6a4
commit c49caead25
11 changed files with 20 additions and 151 deletions
@@ -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 | |
-5
View File
@@ -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;
-8
View File
@@ -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",
-1
View File
@@ -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
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
250 otelLogsFormatting experimental @grafana/observability-logs false false true
251 alertingNotificationHistory experimental @grafana/alerting-squad false false false
252 unifiedStorageSearchDualReaderEnabled experimental @grafana/search-and-storage false false false
dashboardDsAdHocFiltering GA @grafana/datapro false false true
253 dashboardLevelTimeMacros experimental @grafana/dashboards-squad false false true
254 alertmanagerRemoteSecondaryWithRemoteState experimental @grafana/alerting-squad false false false
255 restrictedPluginApis experimental @grafana/plugins-platform-backend false false true
-4
View File
@@ -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"
@@ -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"
}
@@ -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
/>
</EditorField>
@@ -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(
<DashboardQueryEditor
datasource={{} as DashboardDatasource}
query={query}
data={mockPanelData}
onChange={mockOnChange}
onRunQuery={mockOnRunQueries}
/>
);
});
// Wait for any async operations to complete
await waitFor(() => {
expect(screen.queryByText('AdHoc Filters')).not.toBeInTheDocument();
});
});
});
});
@@ -205,15 +205,13 @@ export function DashboardQueryEditor({ data, query, onChange, onRunQuery }: Prop
</Field>
)}
{config.featureToggles.dashboardDsAdHocFiltering && (
<Field
label="AdHoc Filters"
description="Apply --Dashboard-- data source AdHoc filters to this panel"
noMargin
>
<InlineSwitch value={Boolean(query.adHocFiltersEnabled)} onChange={onAdHocFiltersToggle} />
</Field>
)}
<Field
label="AdHoc Filters"
description="Apply --Dashboard-- data source AdHoc filters to this panel"
noMargin
>
<InlineSwitch value={Boolean(query.adHocFiltersEnabled)} onChange={onAdHocFiltersToggle} />
</Field>
</Stack>
{loadingResults ? (
@@ -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: [
@@ -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<DashboardQuery> {
...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<DashboardQuery> {
};
});
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<DashboardQuery> {
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<DashboardQuery> {
async getDrilldownsApplicability(
options?: DataSourceGetDrilldownsApplicabilityOptions<DashboardQuery>
): Promise<DrilldownsApplicability[]> {
if (!config.featureToggles.dashboardDsAdHocFiltering) {
return [];
}
// Check if any query has adhoc filters enabled
const hasAdHocFiltersEnabled = options?.queries?.some((query) => query.adHocFiltersEnabled);