From 3d86d101b7d4737d44b432911689356b392d2827 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Tue, 13 Feb 2024 21:38:26 +0100 Subject: [PATCH] Dashboards: Use `auto` and only use `AdHocFiltersVariable` to manage filters (#81318) --- package.json | 2 +- .../src/selectors/pages.ts | 4 + .../scene/setDashboardPanelContext.test.ts | 20 +-- .../scene/setDashboardPanelContext.ts | 46 ++++--- .../transformSceneToSaveModel.test.ts.snap | 36 +++--- .../sceneVariablesSetToVariables.test.ts | 57 ++++++++ .../sceneVariablesSetToVariables.ts | 10 ++ .../transformSaveModelToScene.test.ts | 69 +++++++++- .../transformSaveModelToScene.ts | 33 +++-- .../transformSceneToSaveModel.ts | 12 -- .../settings/VariablesEditView.test.tsx | 47 +++++-- .../settings/VariablesEditView.tsx | 34 ++--- .../components/AdHocVariableForm.test.tsx | 75 +++++++++++ .../components/AdHocVariableForm.tsx | 34 +++++ .../AdHocFiltersVariableEditor.test.tsx | 122 ++++++++++++++++++ .../editors/AdHocFiltersVariableEditor.tsx | 32 ++++- .../settings/variables/utils.test.ts | 6 +- .../settings/variables/utils.ts | 5 +- .../utils/getVariablesCompatibility.ts | 2 +- .../ActionTabs/AddToFiltersGraphAction.tsx | 4 +- .../app/features/trails/ActionTabs/utils.ts | 2 +- public/app/features/trails/DataTrail.tsx | 4 +- public/app/features/trails/DataTrailCard.tsx | 2 +- .../trails/TrailStore/TrailStore.test.ts | 7 + .../adhoc/AdHocVariableEditor.test.tsx | 90 ------------- .../variables/adhoc/AdHocVariableEditor.tsx | 24 +--- yarn.lock | 10 +- 27 files changed, 538 insertions(+), 251 deletions(-) create mode 100644 public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.test.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.test.tsx delete mode 100644 public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx diff --git a/package.json b/package.json index 49663e1c2d5..ccac6bcdd89 100644 --- a/package.json +++ b/package.json @@ -246,7 +246,7 @@ "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^2.6.5", + "@grafana/scenes": "^3.2.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 4acc843136e..f456dc70576 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -182,6 +182,10 @@ export const Pages = { stepCountIntervalSelect: 'data-testid interval variable step count input', minIntervalInput: 'data-testid interval variable mininum interval input', }, + AdHocFiltersVariable: { + datasourceSelect: Components.DataSourcePicker.inputV2, + infoText: 'data-testid ad-hoc filters variable info text', + }, }, }, }, diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts index 149dce0d119..e0d543c73e2 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts @@ -5,7 +5,7 @@ import { PanelContext } from '@grafana/ui'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { findVizPanelByKey } from '../utils/utils'; -import { getAdHocFilterSetFor, setDashboardPanelContext } from './setDashboardPanelContext'; +import { getAdHocFilterVariableFor, setDashboardPanelContext } from './setDashboardPanelContext'; const postFn = jest.fn(); const putFn = jest.fn(); @@ -132,26 +132,26 @@ describe('setDashboardPanelContext', () => { context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' }); - const set = getAdHocFilterSetFor(scene, { uid: 'my-ds-uid' }); + const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' }); - expect(set.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]); + expect(variable.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]); }); it('Should update and add filter to existing set', () => { - const { scene, context } = buildTestScene({ existingFilterSet: true }); + const { scene, context } = buildTestScene({ existingFilterVariable: true }); - const set = getAdHocFilterSetFor(scene, { uid: 'my-ds-uid' }); + const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' }); - set.setState({ filters: [{ key: 'existing', value: 'world', operator: '=' }] }); + variable.setState({ filters: [{ key: 'existing', value: 'world', operator: '=' }] }); context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' }); - expect(set.state.filters.length).toBe(2); + expect(variable.state.filters.length).toBe(2); // Can update existing filter value without adding a new filter context.onAddAdHocFilter!({ key: 'hello', value: 'world2', operator: '=' }); // Verify existing filter value updated - expect(set.state.filters[1].value).toBe('world2'); + expect(variable.state.filters[1].value).toBe('world2'); }); }); }); @@ -163,7 +163,7 @@ interface SceneOptions { canEdit?: boolean; canDelete?: boolean; orgCanEdit?: boolean; - existingFilterSet?: boolean; + existingFilterVariable?: boolean; } function buildTestScene(options: SceneOptions) { @@ -198,7 +198,7 @@ function buildTestScene(options: SceneOptions) { }, ], templating: { - list: options.existingFilterSet + list: options.existingFilterVariable ? [ { type: 'adhoc', diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts index 5b988c550a8..bb10455fd72 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts @@ -1,5 +1,5 @@ import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data'; -import { AdHocFilterSet, dataLayers, SceneDataLayers, VizPanel } from '@grafana/scenes'; +import { AdHocFiltersVariable, dataLayers, SceneDataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes'; import { DataSourceRef } from '@grafana/schema'; import { AdHocFilterItem, PanelContext } from '@grafana/ui'; import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api'; @@ -111,8 +111,8 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte return; } - const filterSet = getAdHocFilterSetFor(dashboard, queryRunner.state.datasource); - updateAdHocFilterSet(filterSet, newFilter); + const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource); + updateAdHocFilterVariable(filterVar, newFilter); }; context.onUpdateData = (frames: DataFrame[]): Promise => { @@ -149,33 +149,37 @@ function reRunBuiltInAnnotationsLayer(scene: DashboardScene) { } } -export function getAdHocFilterSetFor(scene: DashboardScene, ds: DataSourceRef | null | undefined) { - const controls = scene.state.controls ?? []; +export function getAdHocFilterVariableFor(scene: DashboardScene, ds: DataSourceRef | null | undefined) { + const variables = sceneGraph.getVariables(scene); - for (const control of controls) { - if (control instanceof AdHocFilterSet) { - if (control.state.datasource === ds || control.state.datasource?.uid === ds?.uid) { - return control; + for (const variable of variables.state.variables) { + if (sceneUtils.isAdHocVariable(variable)) { + const filtersDs = variable.state.datasource; + if (filtersDs === ds || filtersDs?.uid === ds?.uid) { + return variable; } } } - const newSet = new AdHocFilterSet({ datasource: ds }); - - // Add it to the scene - scene.setState({ - controls: [controls[0], newSet, ...controls.slice(1)], + const newVariable = new AdHocFiltersVariable({ + name: 'Filters', + datasource: ds, }); - return newSet; + // Add it to the scene + variables.setState({ + variables: [...variables.state.variables, newVariable], + }); + + return newVariable; } -function updateAdHocFilterSet(filterSet: AdHocFilterSet, newFilter: AdHocFilterItem) { +function updateAdHocFilterVariable(filterVar: AdHocFiltersVariable, newFilter: AdHocFilterItem) { // Check if we need to update an existing filter - for (const filter of filterSet.state.filters) { + for (const filter of filterVar.state.filters) { if (filter.key === newFilter.key) { - filterSet.setState({ - filters: filterSet.state.filters.map((f) => { + filterVar.setState({ + filters: filterVar.state.filters.map((f) => { if (f.key === newFilter.key) { return newFilter; } @@ -187,7 +191,7 @@ function updateAdHocFilterSet(filterSet: AdHocFilterSet, newFilter: AdHocFilterI } // Add new filter - filterSet.setState({ - filters: [...filterSet.state.filters, newFilter], + filterVar.setState({ + filters: [...filterVar.state.filters, newFilter], }); } diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap index 2dcb29ae4db..f43d073b626 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap @@ -455,6 +455,16 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho ], "templating": { "list": [ + { + "baseFilters": [], + "datasource": { + "type": "prometheus", + "uid": "wc2AL7L7k", + }, + "filters": [], + "name": "Filters", + "type": "adhoc", + }, { "auto": true, "auto_count": 30, @@ -525,14 +535,6 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho "skipUrlSync": true, "type": "constant", }, - { - "datasource": { - "type": "prometheus", - "uid": "wc2AL7L7k", - }, - "name": "Filters", - "type": "adhoc", - }, ], }, "time": { @@ -757,6 +759,16 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr ], "templating": { "list": [ + { + "baseFilters": [], + "datasource": { + "type": "prometheus", + "uid": "wc2AL7L7k", + }, + "filters": [], + "name": "Filters", + "type": "adhoc", + }, { "auto": true, "auto_count": 30, @@ -827,14 +839,6 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr "skipUrlSync": true, "type": "constant", }, - { - "datasource": { - "type": "prometheus", - "uid": "wc2AL7L7k", - }, - "name": "Filters", - "type": "adhoc", - }, ], }, "time": { diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 112e75b419d..3bb0cc046cc 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -13,6 +13,7 @@ import { } from '@grafana/data'; import { setRunRequest } from '@grafana/runtime'; import { + AdHocFiltersVariable, ConstantVariable, CustomVariable, DataSourceVariable, @@ -344,4 +345,60 @@ describe('sceneVariablesSetToVariables', () => { } `); }); + + it('should handle AdHocFiltersVariable', () => { + const variable = new AdHocFiltersVariable({ + name: 'test', + label: 'test-label', + description: 'test-desc', + datasource: { uid: 'fake-std', type: 'fake-std' }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + }); + const set = new SceneVariableSet({ + variables: [variable], + }); + + const result = sceneVariablesSetToVariables(set); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchInlineSnapshot(` + { + "baseFilters": [ + { + "key": "baseFilterTest", + "operator": "=", + "value": "test", + }, + ], + "datasource": { + "type": "fake-std", + "uid": "fake-std", + }, + "description": "test-desc", + "filters": [ + { + "key": "filterTest", + "operator": "=", + "value": "test", + }, + ], + "label": "test-label", + "name": "test", + "type": "adhoc", + } + `); + }); }); diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index a2145c75777..928afaa5e9b 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -104,6 +104,16 @@ export function sceneVariablesSetToVariables(set: SceneVariables) { }, query: variable.state.value, }); + } else if (sceneUtils.isAdHocVariable(variable)) { + variables.push({ + ...commonProperties, + name: variable.state.name!, + type: 'adhoc', + datasource: variable.state.datasource, + // @ts-expect-error + baseFilters: variable.state.baseFilters, + filters: variable.state.filters, + }); } else { throw new Error('Unsupported variable type'); } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 8e288c8b429..1a6f59fa4d7 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -11,8 +11,9 @@ import { import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { config } from '@grafana/runtime'; import { - AdHocFilterSet, + AdHocFiltersVariable, behaviors, + ConstantVariable, CustomVariable, DataSourceVariable, QueryVariable, @@ -120,11 +121,11 @@ describe('transformSaveModelToScene', () => { expect(scene.state?.$timeRange?.state.timeZone).toEqual('America/New_York'); expect(scene.state?.$timeRange?.state.weekStart).toEqual('saturday'); - expect(scene.state?.$variables?.state.variables).toHaveLength(1); + expect(scene.state?.$variables?.state.variables).toHaveLength(2); + expect(scene.state?.$variables?.getByName('constant')).toBeInstanceOf(ConstantVariable); + expect(scene.state?.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable); expect(dashboardControls).toBeDefined(); expect(dashboardControls).toBeInstanceOf(DashboardControls); - expect(dashboardControls.state.variableControls[1]).toBeInstanceOf(AdHocFilterSet); - expect((dashboardControls.state.variableControls[1] as AdHocFilterSet).state.name).toBe('CoolFilters'); expect(dashboardControls.state.timeControls).toHaveLength(2); expect(dashboardControls.state.timeControls[0]).toBeInstanceOf(SceneTimePicker); expect(dashboardControls.state.timeControls[1]).toBeInstanceOf(SceneRefreshPicker); @@ -789,6 +790,60 @@ describe('transformSaveModelToScene', () => { }); }); + it('should migrate adhoc variable', () => { + const variable: TypedVariableModel = { + id: 'adhoc', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'adhoc', + label: 'Adhoc Label', + description: 'Adhoc Description', + type: 'adhoc', + rootStateKey: 'N4XLmH5Vz', + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + hide: 0, + skipUrlSync: false, + }; + + const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable; + const filterVarState = migrated.state; + + expect(migrated).toBeInstanceOf(AdHocFiltersVariable); + expect(filterVarState).toEqual({ + key: expect.any(String), + description: 'Adhoc Description', + hide: 0, + label: 'Adhoc Label', + name: 'adhoc', + skipUrlSync: false, + type: 'adhoc', + filterExpression: 'filterTest="test"', + filters: [{ key: 'filterTest', operator: '=', value: 'test' }], + baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }], + datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, + applyMode: 'auto', + }); + }); + it.each(['system'])('should throw for unsupported (yet) variables', (type) => { const variable = { name: 'query0', @@ -856,7 +911,7 @@ describe('transformSaveModelToScene', () => { const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); - expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf( + expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( SceneDataLayerControls ); @@ -886,7 +941,7 @@ describe('transformSaveModelToScene', () => { const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); - expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf( + expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( SceneDataLayerControls ); @@ -902,7 +957,7 @@ describe('transformSaveModelToScene', () => { const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); expect(scene.state.$data).toBeInstanceOf(SceneDataLayers); - expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[2]).toBeInstanceOf( + expect((scene.state.controls![0] as DashboardControls)!.state.variableControls[1]).toBeInstanceOf( SceneDataLayerControls ); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index b1dd16f8499..cb3d3795605 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -1,4 +1,4 @@ -import { AdHocVariableModel, TypedVariableModel, VariableModel } from '@grafana/data'; +import { TypedVariableModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { VizPanel, @@ -24,9 +24,9 @@ import { SceneDataLayers, SceneDataLayerProvider, SceneDataLayerControls, - AdHocFilterSet, TextBoxVariable, UserActionEvent, + AdHocFiltersVariable, } from '@grafana/scenes'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking'; @@ -173,24 +173,11 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]): export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) { let variables: SceneVariableSet | undefined = undefined; let layers: SceneDataLayerProvider[] = []; - let filtersSets: AdHocFilterSet[] = []; if (oldModel.templating?.list?.length) { const variableObjects = oldModel.templating.list .map((v) => { try { - if (isAdhocVariable(v)) { - filtersSets.push( - new AdHocFilterSet({ - name: v.name, - datasource: v.datasource, - filters: v.filters ?? [], - baseFilters: v.baseFilters ?? [], - }) - ); - return null; - } - return createSceneVariableFromVariableModel(v); } catch (err) { console.error(err); @@ -282,7 +269,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) : undefined, controls: [ new DashboardControls({ - variableControls: [new VariableValueSelectors({}), ...filtersSets, new SceneDataLayerControls()], + variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], timeControls: [ new SceneTimePicker({}), new SceneRefreshPicker({ @@ -305,6 +292,18 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode name: variable.name, label: variable.label, }; + if (variable.type === 'adhoc') { + return new AdHocFiltersVariable({ + ...commonProperties, + description: variable.description, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + datasource: variable.datasource, + applyMode: 'auto', + filters: variable.filters ?? [], + baseFilters: variable.baseFilters ?? [], + }); + } if (variable.type === 'custom') { return new CustomVariable({ ...commonProperties, @@ -480,8 +479,6 @@ export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike { }); } -const isAdhocVariable = (v: VariableModel): v is AdHocVariableModel => v.type === 'adhoc'; - const getLimitedDescriptionReporter = () => { const reportedPanels: string[] = []; diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 1b9e94cd8a3..84442df1153 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -11,7 +11,6 @@ import { VizPanel, SceneDataTransformer, SceneVariableSet, - AdHocFilterSet, LocalValueVariable, SceneRefreshPicker, } from '@grafana/scenes'; @@ -101,17 +100,6 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa refreshIntervals = control.state.intervals; } } - - const variableControls = state.controls[0].state.variableControls; - for (const control of variableControls) { - if (control instanceof AdHocFilterSet) { - variables.push({ - name: control.state.name!, - type: 'adhoc', - datasource: control.state.datasource, - }); - } - } } if (state.$behaviors) { diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx index 0e71277c26a..8f67633d5a5 100644 --- a/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.test.tsx @@ -10,7 +10,14 @@ import { } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { setPluginImportUtils, setRunRequest } from '@grafana/runtime'; -import { SceneVariableSet, CustomVariable, SceneGridItem, SceneGridLayout, VizPanel } from '@grafana/scenes'; +import { + SceneVariableSet, + CustomVariable, + SceneGridItem, + SceneGridLayout, + VizPanel, + AdHocFiltersVariable, +} from '@grafana/scenes'; import { mockDataSource } from 'app/features/alerting/unified/mocks'; import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor'; @@ -97,11 +104,16 @@ describe('VariablesEditView', () => { query: 'test3, test4, $customVar', value: 'test3', }, + { + type: 'adhoc', + name: 'adhoc', + }, ]; const variables = variableView.getVariables(); - expect(variables).toHaveLength(2); + expect(variables).toHaveLength(3); expect(variables[0].state).toMatchObject(expectedVariables[0]); expect(variables[1].state).toMatchObject(expectedVariables[1]); + expect(variables[2].state).toMatchObject(expectedVariables[2]); }); }); @@ -117,7 +129,7 @@ describe('VariablesEditView', () => { const variables = variableView.getVariables(); const variable = variables[0]; variableView.onDuplicated(variable.state.name); - expect(variableView.getVariables()).toHaveLength(3); + expect(variableView.getVariables()).toHaveLength(4); expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar'); }); @@ -125,7 +137,7 @@ describe('VariablesEditView', () => { const variableIdentifier = 'customVar'; variableView.onDuplicated(variableIdentifier); variableView.onDuplicated(variableIdentifier); - expect(variableView.getVariables()).toHaveLength(4); + expect(variableView.getVariables()).toHaveLength(5); expect(variableView.getVariables()[1].state.name).toBe('copy_of_customVar_1'); expect(variableView.getVariables()[2].state.name).toBe('copy_of_customVar'); }); @@ -133,7 +145,7 @@ describe('VariablesEditView', () => { it('should delete a variable', () => { const variableIdentifier = 'customVar'; variableView.onDelete(variableIdentifier); - expect(variableView.getVariables()).toHaveLength(1); + expect(variableView.getVariables()).toHaveLength(2); expect(variableView.getVariables()[0].state.name).toBe('customVar2'); }); @@ -147,7 +159,7 @@ describe('VariablesEditView', () => { it('should keep the same order of variables with invalid indexes', () => { const fromIndex = 0; - const toIndex = 2; + const toIndex = 3; const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); @@ -163,11 +175,11 @@ describe('VariablesEditView', () => { const previousVariable = variableView.getVariables()[1] as CustomVariable; variableView.onEdit('customVar2'); - variableView.onTypeChange('constant'); - expect(variableView.getVariables()).toHaveLength(2); + variableView.onTypeChange('adhoc'); + expect(variableView.getVariables()).toHaveLength(3); const variable = variableView.getVariables()[1]; expect(variable).not.toBe(previousVariable); - expect(variable.state.type).toBe('constant'); + expect(variable.state.type).toBe('adhoc'); // Values to be kept between the old and new variable expect(variable.state.name).toEqual(previousVariable.state.name); @@ -184,9 +196,9 @@ describe('VariablesEditView', () => { it('should add default new query variable when onAdd is called', () => { variableView.onAdd(); - expect(variableView.getVariables()).toHaveLength(3); - expect(variableView.getVariables()[2].state.name).toBe('query0'); - expect(variableView.getVariables()[2].state.type).toBe('query'); + expect(variableView.getVariables()).toHaveLength(4); + expect(variableView.getVariables()[3].state.name).toBe('query0'); + expect(variableView.getVariables()[3].state.type).toBe('query'); }); afterEach(() => { @@ -266,6 +278,17 @@ async function buildTestScene() { value: '$customVar', text: '$customVar', }), + new AdHocFiltersVariable({ + type: 'adhoc', + name: 'adhoc', + filters: [ + { + key: 'test', + operator: '=', + value: 'testValue', + }, + ], + }), ], }), body: new SceneGridLayout({ diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx index d574cbdd5c5..84fe03a8f93 100644 --- a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx @@ -1,14 +1,7 @@ import React from 'react'; import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; -import { - SceneComponentProps, - SceneObjectBase, - SceneVariable, - SceneVariables, - sceneGraph, - AdHocFilterSet, -} from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectBase, SceneVariable, SceneVariables, sceneGraph } from '@grafana/scenes'; import { Page } from 'app/core/components/Page/Page'; import { DashboardScene } from '../scene/DashboardScene'; @@ -46,7 +39,7 @@ export class VariablesEditView extends SceneObjectBase i return variables.findIndex((variable) => variable.state.name === identifier); }; - private replaceEditVariable = (newVariable: SceneVariable | AdHocFilterSet) => { + private replaceEditVariable = (newVariable: SceneVariable) => { // Find the index of the variable to be deleted const variableIndex = this.state.editIndex ?? -1; const { variables } = this.getVariableSet().state; @@ -58,18 +51,10 @@ export class VariablesEditView extends SceneObjectBase i return; } - if (newVariable instanceof AdHocFilterSet) { - // TODO: Update controls in adding this fiter set to the dashboard - } else { - const updatedVariables = [ - ...variables.slice(0, variableIndex), - newVariable, - ...variables.slice(variableIndex + 1), - ]; + const updatedVariables = [...variables.slice(0, variableIndex), newVariable, ...variables.slice(variableIndex + 1)]; - // Update the state or the variables array - this.getVariableSet().setState({ variables: updatedVariables }); - } + // Update the state or the variables array + this.getVariableSet().setState({ variables: updatedVariables }); }; public onDelete = (identifier: string) => { @@ -158,12 +143,9 @@ export class VariablesEditView extends SceneObjectBase i const variableIndex = variables.length; //add the new variable to the end of the array const defaultNewVariable = getVariableDefault(variables); - if (defaultNewVariable instanceof AdHocFilterSet) { - // TODO: Update controls in adding this fiter set to the dashboard - } else { - this.getVariableSet().setState({ variables: [...this.getVariables(), defaultNewVariable] }); - this.setState({ editIndex: variableIndex }); - } + + this.getVariableSet().setState({ variables: [...this.getVariables(), defaultNewVariable] }); + this.setState({ editIndex: variableIndex }); }; public onTypeChange = (type: EditableVariableType) => { diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.test.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.test.tsx new file mode 100644 index 00000000000..498483266d4 --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.test.tsx @@ -0,0 +1,75 @@ +import { act, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; +import { mockDataSource } from 'app/features/alerting/unified/mocks'; + +import { AdHocVariableForm } from './AdHocVariableForm'; + +const defaultDatasource = mockDataSource({ + name: 'Default Test Data Source', + uid: 'test-ds', + type: 'test', +}); + +const promDatasource = mockDataSource({ + name: 'Prometheus', + uid: 'prometheus', + type: 'prometheus', +}); + +jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({ + ...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'), + getDataSourceSrv: () => ({ + get: async () => defaultDatasource, + getList: () => [defaultDatasource, promDatasource], + getInstanceSettings: () => ({ ...defaultDatasource }), + }), +})); + +describe('AdHocVariableForm', () => { + it('should render the form with the provided data source', async () => { + const onDataSourceChange = jest.fn(); + const { renderer } = await setup({ + datasource: defaultDatasource, + onDataSourceChange, + infoText: 'Test Info', + }); + + const dataSourcePicker = renderer.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect + ); + const infoText = renderer.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText + ); + + expect(dataSourcePicker).toBeInTheDocument(); + expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source'); + expect(infoText).toBeInTheDocument(); + expect(infoText).toHaveTextContent('Test Info'); + }); + + it('should call the onDataSourceChange callback when the data source is changed', async () => { + const onDataSourceChange = jest.fn(); + const { renderer, user } = await setup({ + datasource: defaultDatasource, + onDataSourceChange, + infoText: 'Test Info', + }); + + // Simulate changing the data source + await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2)); + await user.click(renderer.getByText(/prom/i)); + + expect(onDataSourceChange).toHaveBeenCalledTimes(1); + expect(onDataSourceChange).toHaveBeenCalledWith(promDatasource, undefined); + }); +}); + +async function setup(props?: React.ComponentProps) { + return { + renderer: await act(() => render()), + user: userEvent.setup(), + }; +} diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx new file mode 100644 index 00000000000..fe093a380b5 --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { DataSourceInstanceSettings } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { DataSourceRef } from '@grafana/schema'; +import { Alert, Field } from '@grafana/ui'; +import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; + +import { VariableLegend } from './VariableLegend'; + +interface AdHocVariableFormProps { + datasource?: DataSourceRef; + onDataSourceChange: (dsSettings: DataSourceInstanceSettings) => void; + infoText?: string; +} + +export function AdHocVariableForm({ datasource, infoText, onDataSourceChange }: AdHocVariableFormProps) { + return ( + <> + Ad-hoc options + + + + + {infoText ? ( + + ) : null} + + ); +} diff --git a/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.test.tsx b/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.test.tsx new file mode 100644 index 00000000000..8fae3e3fe2a --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.test.tsx @@ -0,0 +1,122 @@ +import { render, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { of } from 'rxjs'; + +import { + FieldType, + LoadingState, + PanelData, + VariableSupportType, + getDefaultTimeRange, + toDataFrame, +} from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { setRunRequest } from '@grafana/runtime/src'; +import { AdHocFiltersVariable } from '@grafana/scenes'; +import { mockDataSource } from 'app/features/alerting/unified/mocks'; +import { LegacyVariableQueryEditor } from 'app/features/variables/editor/LegacyVariableQueryEditor'; + +import { AdHocFiltersVariableEditor } from './AdHocFiltersVariableEditor'; + +const defaultDatasource = mockDataSource({ + name: 'Default Test Data Source', + uid: 'test-ds', + type: 'test', +}); + +const promDatasource = mockDataSource({ + name: 'Prometheus', + uid: 'prometheus', + type: 'prometheus', +}); + +jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => ({ + ...jest.requireActual('@grafana/runtime/src/services/dataSourceSrv'), + getDataSourceSrv: () => ({ + get: async () => ({ + ...defaultDatasource, + variables: { + getType: () => VariableSupportType.Custom, + query: jest.fn(), + editor: jest.fn().mockImplementation(LegacyVariableQueryEditor), + }, + }), + getList: () => [defaultDatasource, promDatasource], + getInstanceSettings: () => ({ ...defaultDatasource }), + }), +})); + +const runRequestMock = jest.fn().mockReturnValue( + of({ + state: LoadingState.Done, + series: [ + toDataFrame({ + fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }], + }), + ], + timeRange: getDefaultTimeRange(), + }) +); + +setRunRequest(runRequestMock); + +describe('AdHocFiltersVariableEditor', () => { + it('renders AdHocVariableForm with correct props', async () => { + const { renderer } = await setup(); + const dataSourcePicker = renderer.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.datasourceSelect + ); + const infoText = renderer.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.AdHocFiltersVariable.infoText + ); + + expect(dataSourcePicker).toBeInTheDocument(); + expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source'); + expect(infoText).toBeInTheDocument(); + expect(infoText).toHaveTextContent('This data source does not support ad hoc filters yet.'); + }); + + it('should update the variable data source when data source picker is changed', async () => { + const { renderer, variable, user } = await setup(); + + // Simulate changing the data source + await user.click(renderer.getByTestId(selectors.components.DataSourcePicker.inputV2)); + await user.click(renderer.getByText(/prom/i)); + + expect(variable.state.datasource).toEqual({ uid: 'prometheus', type: 'prometheus' }); + }); +}); + +async function setup(props?: React.ComponentProps) { + const onRunQuery = jest.fn(); + const variable = new AdHocFiltersVariable({ + name: 'adhocVariable', + type: 'adhoc', + label: 'Ad hoc filters', + description: 'Ad hoc filters are applied automatically to all queries that target this data source', + datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type }, + filters: [ + { + key: 'test', + operator: '=', + value: 'testValue', + }, + ], + baseFilters: [ + { + key: 'baseTest', + operator: '=', + value: 'baseTestValue', + }, + ], + }); + return { + renderer: await act(() => + render() + ), + variable, + user: userEvent.setup(), + mocks: { onRunQuery }, + }; +} diff --git a/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.tsx index 15a50cffbfc..37daff7cb56 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/AdHocFiltersVariableEditor.tsx @@ -1,12 +1,40 @@ import React from 'react'; +import { useAsync } from 'react-use'; +import { DataSourceInstanceSettings } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable } from '@grafana/scenes'; +import { DataSourceRef } from '@grafana/schema'; + +import { AdHocVariableForm } from '../components/AdHocVariableForm'; interface AdHocFiltersVariableEditorProps { variable: AdHocFiltersVariable; - onChange: (variable: AdHocFiltersVariable) => void; + onRunQuery: (variable: AdHocFiltersVariable) => void; } export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProps) { - return
AdHocFiltersVariableEditor
; + const { variable } = props; + const datasourceRef = variable.useState().datasource ?? undefined; + + const { value: datasourceSettings } = useAsync(async () => { + return await getDataSourceSrv().get(datasourceRef); + }, [datasourceRef]); + + const message = datasourceSettings?.getTagKeys + ? 'Ad hoc filters are applied automatically to all queries that target this data source' + : 'This data source does not support ad hoc filters yet.'; + + const onDataSourceChange = (ds: DataSourceInstanceSettings) => { + const dsRef: DataSourceRef = { + uid: ds.uid, + type: ds.type, + }; + + variable.setState({ + datasource: dsRef, + }); + }; + + return ; } diff --git a/public/app/features/dashboard-scene/settings/variables/utils.test.ts b/public/app/features/dashboard-scene/settings/variables/utils.test.ts index f34dcdc3ec1..2b2c42d1ca8 100644 --- a/public/app/features/dashboard-scene/settings/variables/utils.test.ts +++ b/public/app/features/dashboard-scene/settings/variables/utils.test.ts @@ -172,10 +172,10 @@ describe('getVariableScene', () => { ['adhoc', AdHocFiltersVariable], ['groupby', GroupByVariable], ['textbox', TextBoxVariable], - ])('should return the scene variable instance for the given editable variable type', () => { + ])('should return the scene variable instance for the given editable variable type', (type, instanceType) => { const initialState = { name: 'MyVariable' }; - const sceneVariable = getVariableScene('custom', initialState); - expect(sceneVariable).toBeInstanceOf(CustomVariable); + const sceneVariable = getVariableScene(type as EditableVariableType, initialState); + expect(sceneVariable).toBeInstanceOf(instanceType); expect(sceneVariable.state.name).toBe(initialState.name); }); }); diff --git a/public/app/features/dashboard-scene/settings/variables/utils.ts b/public/app/features/dashboard-scene/settings/variables/utils.ts index f1ed0380100..1d61b445b6b 100644 --- a/public/app/features/dashboard-scene/settings/variables/utils.ts +++ b/public/app/features/dashboard-scene/settings/variables/utils.ts @@ -9,10 +9,10 @@ import { IntervalVariable, TextBoxVariable, QueryVariable, - AdHocFilterSet, GroupByVariable, SceneVariable, MultiValueVariable, + AdHocFiltersVariable, SceneVariableState, } from '@grafana/scenes'; import { VariableType } from '@grafana/schema'; @@ -124,8 +124,7 @@ export function getVariableScene(type: EditableVariableType, initialState: Commo case 'datasource': return new DataSourceVariable(initialState); case 'adhoc': - // TODO: Initialize properly AdHocFilterSet with initialState - return new AdHocFilterSet({ name: initialState.name }); + return new AdHocFiltersVariable(initialState); case 'groupby': return new GroupByVariable(initialState); case 'textbox': diff --git a/public/app/features/dashboard-scene/utils/getVariablesCompatibility.ts b/public/app/features/dashboard-scene/utils/getVariablesCompatibility.ts index ed26220201f..54c47284639 100644 --- a/public/app/features/dashboard-scene/utils/getVariablesCompatibility.ts +++ b/public/app/features/dashboard-scene/utils/getVariablesCompatibility.ts @@ -10,7 +10,7 @@ export function getVariablesCompatibility(sceneObject: SceneObject): TypedVariab // Sadly templateSrv.getVariables returns TypedVariableModel but sceneVariablesSetToVariables return persisted schema model // They look close to identical (differ in what is optional in some places). // The way templateSrv.getVariables is used it should not matter. it is mostly used to get names of all variables (for query editors). - // So type and name are important. Maybe some external data sourcess also check current value so that is also important. + // So type and name are important. Maybe some external data sources also check current value so that is also important. // @ts-expect-error return legacyModels; } diff --git a/public/app/features/trails/ActionTabs/AddToFiltersGraphAction.tsx b/public/app/features/trails/ActionTabs/AddToFiltersGraphAction.tsx index f92ef303f92..9a71488e30f 100644 --- a/public/app/features/trails/ActionTabs/AddToFiltersGraphAction.tsx +++ b/public/app/features/trails/ActionTabs/AddToFiltersGraphAction.tsx @@ -28,9 +28,9 @@ export class AddToFiltersGraphAction extends SceneObjectBase ({ + ...jest.requireActual('@grafana/runtime'), + getTemplateSrv: () => ({ + getAdhocFilters: jest.fn().mockReturnValue([{ key: 'origKey', operator: '=', value: '' }]), + }), +})); + describe('TrailStore', () => { beforeAll(() => { let localStore: Record = {}; diff --git a/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx b/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx deleted file mode 100644 index 87e640539ac..00000000000 --- a/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { ComponentProps } from 'react'; - -import { selectors } from '@grafana/e2e-selectors'; -import { mockDataSource } from 'app/features/alerting/unified/mocks'; -import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; - -import { adHocBuilder } from '../shared/testing/builders'; - -import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor'; - -const promDsMock = mockDataSource({ - name: 'Prometheus', - type: DataSourceType.Prometheus, -}); - -const lokiDsMock = mockDataSource({ - name: 'Loki', - type: DataSourceType.Loki, -}); - -jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { - return { - getDataSourceSrv: () => ({ - get: () => { - return Promise.resolve(promDsMock); - }, - getList: () => [promDsMock, lokiDsMock], - getInstanceSettings: (v: string) => { - if (v === 'Prometheus') { - return promDsMock; - } - return lokiDsMock; - }, - }), - }; -}); - -const props = { - extended: { - dataSources: [ - { text: 'Prometheus', value: null }, // default datasource - { text: 'Loki', value: { type: 'loki-ds', uid: 'abc' } }, - ], - } as ComponentProps['extended'], - variable: adHocBuilder().withId('adhoc').withRootStateKey('key').withName('adhoc').build(), - onPropChange: jest.fn(), - - // connected actions - initAdHocVariableEditor: jest.fn(), - changeVariableDatasource: jest.fn(), -}; - -describe('AdHocVariableEditor', () => { - beforeEach(() => { - props.changeVariableDatasource.mockReset(); - }); - - it('has a datasource select menu', async () => { - render(); - - expect(await screen.getByTestId(selectors.components.DataSourcePicker.container)).toBeInTheDocument(); - }); - - it('calls the callback when changing the datasource', async () => { - render(); - const selectEl = screen - .getByTestId(selectors.components.DataSourcePicker.container) - .getElementsByTagName('input')[0]; - await userEvent.click(selectEl); - await userEvent.click(screen.getByText('Loki')); - - expect(props.changeVariableDatasource).toBeCalledWith( - { type: 'adhoc', id: 'adhoc', rootStateKey: 'key' }, - { type: 'loki', uid: 'mock-ds-3' } - ); - }); - - it('renders informational text', () => { - const extended = { - ...props.extended, - infoText: "Here's a message that should help you", - }; - render(); - - const alert = screen.getByText("Here's a message that should help you"); - expect(alert).toBeInTheDocument(); - }); -}); diff --git a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx index ac079a2d787..8bd63d44309 100644 --- a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx +++ b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx @@ -2,11 +2,9 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data'; -import { Alert, Field } from '@grafana/ui'; -import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; +import { AdHocVariableForm } from 'app/features/dashboard-scene/settings/variables/components/AdHocVariableForm'; import { StoreState } from 'app/types'; -import { VariableLegend } from '../../dashboard-scene/settings/variables/components/VariableLegend'; import { initialVariableEditorState } from '../editor/reducer'; import { getAdhocVariableEditorState } from '../editor/selectors'; import { VariableEditorProps } from '../editor/types'; @@ -58,23 +56,13 @@ export class AdHocVariableEditorUnConnected extends PureComponent { render() { const { variable, extended } = this.props; - const infoText = extended?.infoText ?? null; return ( - <> - Ad-hoc options - - - - - {infoText ? : null} - + ); } } diff --git a/yarn.lock b/yarn.lock index edb666051bf..a3bfcbd3626 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4020,9 +4020,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^2.6.5": - version: 2.6.5 - resolution: "@grafana/scenes@npm:2.6.5" +"@grafana/scenes@npm:^3.2.1": + version: 3.2.1 + resolution: "@grafana/scenes@npm:3.2.1" dependencies: "@grafana/e2e-selectors": "npm:10.0.2" react-grid-layout: "npm:1.3.4" @@ -4034,7 +4034,7 @@ __metadata: "@grafana/runtime": 10.0.3 "@grafana/schema": 10.0.3 "@grafana/ui": 10.0.3 - checksum: 10/68fe91a5a0c8f80b679126f3525b74b29ce3f9ad92bc558eaaf39693235137348b90796c2b02aa7c1c7929586e60df6024e139993a416ef37d4c875e548dc855 + checksum: 10/5e93c0dcdfbd7cfed977d650fb0744c9b8107b1c92af2ae6d6e9f2f61bb9ba7f3cb5ad394fa3389aaef0d892a18c6a12fedf5454a4210bdcc6797a272ddbc625 languageName: node linkType: hard @@ -18026,7 +18026,7 @@ __metadata: "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^2.6.5" + "@grafana/scenes": "npm:^3.2.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1"