diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 200d3bee3b1..f35151dbf3f 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -177,4 +177,5 @@ export interface FeatureToggles { ssoSettingsSAML?: boolean; usePrometheusFrontendPackage?: boolean; oauthRequireSubClaim?: boolean; + newDashboardWithFiltersAndGroupBy?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index b9ac8ec76e4..fdcef526bd8 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1189,6 +1189,15 @@ var ( HideFromDocs: true, HideFromAdminPage: true, }, + { + Name: "newDashboardWithFiltersAndGroupBy", + Description: "Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering.", + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + AllowSelfServe: false, + HideFromDocs: true, + HideFromAdminPage: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index e873af56b20..a54bc887b3a 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -158,3 +158,4 @@ scopeFilters,experimental,@grafana/dashboards-squad,false,false,false ssoSettingsSAML,experimental,@grafana/identity-access-team,false,false,false usePrometheusFrontendPackage,experimental,@grafana/observability-metrics,false,false,true oauthRequireSubClaim,experimental,@grafana/identity-access-team,false,false,false +newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 5b04e68f788..475b996c633 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -642,4 +642,8 @@ const ( // FlagOauthRequireSubClaim // Require that sub claims is present in oauth tokens. FlagOauthRequireSubClaim = "oauthRequireSubClaim" + + // FlagNewDashboardWithFiltersAndGroupBy + // Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering. + FlagNewDashboardWithFiltersAndGroupBy = "newDashboardWithFiltersAndGroupBy" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 7fa7d4cc01d..b54a2456ba7 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2078,6 +2078,35 @@ "hideFromAdminPage": true, "hideFromDocs": true } + }, + { + "metadata": { + "name": "filtersOnByDefault", + "resourceVersion": "1712149621080", + "creationTimestamp": "2024-04-03T13:07:01Z", + "deletionTimestamp": "2024-04-04T10:35:56Z" + }, + "spec": { + "description": "Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering.", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "newDashboardWithFiltersAndGroupBy", + "resourceVersion": "1712226956055", + "creationTimestamp": "2024-04-04T10:35:56Z" + }, + "spec": { + "description": "Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering.", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } } ] } \ No newline at end of file diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 73e4a4f6d48..6fa5474fce2 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -84,7 +84,8 @@ export class DashboardScenePageStateManager extends StateManagerBase ({ type: 'fake-std', uid: 'fake-std' }), + query: () => + Promise.resolve({ + data: [], + }), + testDatasource: () => Promise.resolve({ status: 'success', message: 'abc' }), + meta: { + id: 'fake-std', + type: PluginType.datasource, + module: 'fake-std', + baseUrl: '', + name: 'fake-std', + info: { + author: { name: '' }, + description: '', + links: [], + logos: { large: '', small: '' }, + updated: '', + version: '', + screenshots: [], + }, + }, + // Standard variable support + variables: { + getType: () => VariableSupportType.Standard, + toDataQuery: (q) => ({ ...q, refId: 'FakeDataSource-refId' }), + }, + getTagKeys: jest.fn(), + id: 1, + uid: 'fake-std', +}; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + featureToggles: { + newDashboardWithFiltersAndGroupBy: false, + }, + }, + getDataSourceSrv: () => ({ + get: (): Promise => { + return Promise.resolve(fakeDsMock); + }, + }), +})); + +describe('buildNewDashboardSaveModel', () => { + it('should not have template variables defined by default', async () => { + const result = await buildNewDashboardSaveModel(); + expect(result.dashboard.templating).toBeUndefined(); + }); + + describe('when featureToggles.newDashboardWithFiltersAndGroupBy is true', () => { + beforeAll(() => { + config.featureToggles.newDashboardWithFiltersAndGroupBy = true; + }); + afterAll(() => { + config.featureToggles.newDashboardWithFiltersAndGroupBy = false; + }); + + it('should add filter and group by variables if the datasource supports it and is set as default', async () => { + const result = await buildNewDashboardSaveModel(); + expect(result.dashboard.templating?.list).toHaveLength(2); + expect(result.dashboard.templating?.list?.[0].type).toBe('adhoc'); + expect(result.dashboard.templating?.list?.[1].type).toBe('groupby'); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts b/public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts index dcb7460f788..a4848b25e3c 100644 --- a/public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts @@ -1,7 +1,37 @@ -import { defaultDashboard } from '@grafana/schema'; +import { config } from '@grafana/runtime'; +import { VariableModel, defaultDashboard } from '@grafana/schema'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DashboardDTO } from 'app/types'; -export function buildNewDashboardSaveModel(urlFolderUid?: string): DashboardDTO { +export async function buildNewDashboardSaveModel(urlFolderUid?: string): Promise { + let variablesList = defaultDashboard.templating?.list; + + if (config.featureToggles.newDashboardWithFiltersAndGroupBy) { + // Add filter and group by variables if the datasource supports it + const defaultDs = await getDatasourceSrv().get(); + + if (defaultDs.getTagKeys) { + const datasourceRef = { + type: defaultDs.meta.id, + uid: defaultDs.uid, + }; + + const fitlerVariable: VariableModel = { + datasource: datasourceRef, + name: 'Filter', + type: 'adhoc', + }; + + const groupByVariable: VariableModel = { + datasource: datasourceRef, + name: 'Group by', + type: 'groupby', + }; + + variablesList = (variablesList || []).concat([fitlerVariable, groupByVariable]); + } + } + const data: DashboardDTO = { meta: { canStar: false, @@ -18,6 +48,12 @@ export function buildNewDashboardSaveModel(urlFolderUid?: string): DashboardDTO }, }; + if (variablesList) { + data.dashboard.templating = { + list: variablesList, + }; + } + if (urlFolderUid) { data.meta.folderUid = urlFolderUid; } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 136d33b281c..52aef28315c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -177,8 +177,8 @@ describe('transformSaveModelToScene', () => { }); describe('When creating a new dashboard', () => { - it('should initialize the DashboardScene in edit mode and dirty', () => { - const rsp = buildNewDashboardSaveModel(); + it('should initialize the DashboardScene in edit mode and dirty', async () => { + const rsp = await buildNewDashboardSaveModel(); const scene = transformSaveModelToScene(rsp); expect(scene.state.isEditing).toBe(undefined); expect(scene.state.isDirty).toBe(false); diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index a3fa2238856..af312e80738 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -120,7 +120,7 @@ async function fetchDashboard( if (args.urlFolderUid) { await dispatch(getFolderByUid(args.urlFolderUid)); } - return buildNewDashboardSaveModel(args.urlFolderUid); + return await buildNewDashboardSaveModel(args.urlFolderUid); } case DashboardRoutes.Path: { const path = args.urlSlug ?? ''; diff --git a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts index 58bf7336df4..7dad742e5ef 100644 --- a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts +++ b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts @@ -85,7 +85,7 @@ export async function setDashboardInLocalStorage(options: AddPanelToDashboardOpt throw AddToDashboardError.FETCH_DASHBOARD; } } else { - dto = buildNewDashboardSaveModel(); + dto = await buildNewDashboardSaveModel(); } dto.dashboard.panels = [panel, ...(dto.dashboard.panels ?? [])];