From da84148dc566438e14f1e69488850415f942a16e Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Wed, 5 Nov 2025 15:24:14 +0200 Subject: [PATCH] groupBy per panel action tests --- package.json | 4 +- .../scene/PanelGroupByAction.test.tsx | 213 ++++++++++++++++++ yarn.lock | 22 +- 3 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 public/app/features/dashboard-scene/scene/PanelGroupByAction.test.tsx diff --git a/package.json b/package.json index 5d7dd234e25..46187aad10e 100644 --- a/package.json +++ b/package.json @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.42.1", - "@grafana/scenes-react": "^6.42.1", + "@grafana/scenes": "6.43.0--canary.1278.19036459049.0", + "@grafana/scenes-react": "6.43.0--canary.1278.19036459049.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/scene/PanelGroupByAction.test.tsx b/public/app/features/dashboard-scene/scene/PanelGroupByAction.test.tsx new file mode 100644 index 00000000000..a7eafa87c3d --- /dev/null +++ b/public/app/features/dashboard-scene/scene/PanelGroupByAction.test.tsx @@ -0,0 +1,213 @@ +import { of } from 'rxjs'; + +import { GroupByVariable, SceneQueryRunner, SceneVariableSet, VizPanel } from '@grafana/scenes'; + +import { DashboardScene } from './DashboardScene'; +import { PanelGroupByAction } from './PanelGroupByAction'; +import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: () => { + return { + get: jest.fn().mockResolvedValue({}), + getInstanceSettings: jest.fn().mockResolvedValue({ uid: 'ds1' }), + }; + }, + getPluginImportUtils: () => ({ + getPanelPluginFromCache: jest.fn(() => undefined), + }), +})); + +describe('PanelGroupByAction', () => { + describe('Initialization', () => { + it('should create PanelGroupByAction instance', () => { + const action = new PanelGroupByAction(); + expect(action).toBeDefined(); + expect(action).toBeInstanceOf(PanelGroupByAction); + }); + + it('should find GroupByVariable after activation', () => { + const { action, variable } = buildTestScene(); + + // Mock validateAndUpdate to avoid hanging + jest.spyOn(variable, 'validateAndUpdate').mockReturnValue( + of({ + origin: variable, + state: variable.state, + }) + ); + + const deactivate = action.activate(); + + const groupByVar = action.getGroupByVariable(); + expect(groupByVar).toBeDefined(); + expect(groupByVar).toBeInstanceOf(GroupByVariable); + expect(groupByVar?.state.name).toBe('A'); + + deactivate(); + }); + + describe('GroupBy variable integration', () => { + it('should access variable through scene graph', () => { + const { action, variable } = buildTestScene(); + + jest.spyOn(variable, 'validateAndUpdate').mockReturnValue( + of({ + origin: variable, + state: variable.state, + }) + ); + + const deactivate = action.activate(); + + const foundVariable = action.getGroupByVariable(); + expect(foundVariable).toBe(variable); + expect(foundVariable?.state.type).toBe('groupby'); + + deactivate(); + }); + + it('should handle missing GroupByVariable gracefully', () => { + const action = new PanelGroupByAction(); + const panel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + headerActions: [action], + $data: new SceneQueryRunner({ + datasource: { uid: 'my-uid' }, + queries: [{ query: 'QueryA', refId: 'A' }], + }), + }); + + new DashboardScene({ + uid: 'dash-1', + $variables: new SceneVariableSet({ + variables: [], // No GroupByVariable + }), + body: DefaultGridLayoutManager.fromVizPanels([panel]), + }); + + const deactivate = action.activate(); + + expect(action.getGroupByVariable()).toBeUndefined(); + + deactivate(); + }); + }); + + describe('Panel parent requirement', () => { + it('should throw error if parent is not VizPanel', () => { + const action = new PanelGroupByAction(); + + expect(() => { + action.activate(); + }).toThrow('PanelGroupByAction can be used only for VizPanel'); + }); + }); + + describe('getGroupByOptions', () => { + it('should return empty array when variable has no options', async () => { + const { action, variable } = buildTestScene(); + + jest.spyOn(variable, 'validateAndUpdate').mockReturnValue( + of({ + origin: variable, + state: { + ...variable.state, + options: [], + }, + }) + ); + + const deactivate = action.activate(); + + const options = await action.getGroupByOptions(); + expect(options).toEqual([]); + + deactivate(); + }); + + it('should filter options based on applicability', async () => { + const { action, variable, panel } = buildTestScene(); + + variable.setState({ + options: [ + { label: 'field1', value: 'field1' }, + { label: 'field2', value: 'field2' }, + { label: 'field3', value: 'field3' }, + ], + }); + + const queryRunner = panel.state.$data as SceneQueryRunner; + queryRunner.setState({ + data: { + state: 'Done', + series: [], + timeRange: {} as any, + request: { + targets: [{ refId: 'A', datasource: { uid: 'test-uid', type: 'prometheus' } }], + }, + }, + }); + + jest.spyOn(variable, 'validateAndUpdate').mockReturnValue( + of({ + origin: variable, + state: variable.state, + }) + ); + + jest.spyOn(variable, 'getGroupByApplicabilityForQueries').mockResolvedValue([ + { key: 'field1', applicable: true }, + { key: 'field2', applicable: false }, + { key: 'field3', applicable: true }, + ]); + + const deactivate = action.activate(); + + const options = await action.getGroupByOptions(); + expect(options).toHaveLength(2); + expect(options).toEqual([ + { label: 'field1', value: 'field1' }, + { label: 'field3', value: 'field3' }, + ]); + + deactivate(); + }); + }); +}); + +function buildTestScene() { + const variable = new GroupByVariable({ + name: 'A', + label: 'A', + description: 'A', + type: 'groupby', + value: 'Text', + }); + + const action = new PanelGroupByAction(); + + const panel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-12', + headerActions: [action], + $data: new SceneQueryRunner({ + datasource: { uid: 'my-uid' }, + queries: [{ query: 'QueryA', refId: 'A' }], + }), + }); + + const dashboard = new DashboardScene({ + uid: 'A', + $variables: new SceneVariableSet({ + variables: [variable], + }), + body: DefaultGridLayoutManager.fromVizPanels([panel]), + }); + + return { action, panel, dashboard, variable }; +} diff --git a/yarn.lock b/yarn.lock index 9a61d066022..e35f4fefc64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3555,11 +3555,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.42.1": - version: 6.42.1 - resolution: "@grafana/scenes-react@npm:6.42.1" +"@grafana/scenes-react@npm:6.43.0--canary.1278.19036459049.0": + version: 6.43.0--canary.1278.19036459049.0 + resolution: "@grafana/scenes-react@npm:6.43.0--canary.1278.19036459049.0" dependencies: - "@grafana/scenes": "npm:6.42.1" + "@grafana/scenes": "npm:6.43.0--canary.1278.19036459049.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3571,7 +3571,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/190289561d343a7a2d9d036f9b23abb063cee20f3a3e9e9275d8f11ef2e0df9fbd256e4687bf5a7b8f59aa79ae2bbc05bbb009876f582ebaeed97a0f52492907 + checksum: 10/46d746bcbd83f903672d44f0dbc97fe2c400865c93847e6debeec554ba7c587995219a91868d24984dd35b2700590b6ff14dd523f28b2d69b64ce2e81feb4694 languageName: node linkType: hard @@ -3601,9 +3601,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.42.1, @grafana/scenes@npm:^6.42.1": - version: 6.42.1 - resolution: "@grafana/scenes@npm:6.42.1" +"@grafana/scenes@npm:6.43.0--canary.1278.19036459049.0": + version: 6.43.0--canary.1278.19036459049.0 + resolution: "@grafana/scenes@npm:6.43.0--canary.1278.19036459049.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3623,7 +3623,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/f1f455f823c01324942fef9feb256b04cb209d486a43e6dd683e984abb88f078d8e2a9d85e31619e37c9c3ee4356c97707d258a608f9b64ae8df097813bf178f + checksum: 10/6cb9d0bb259afd4ee4c15db5668e70300eb0ef68f90c1823a4106c487ba6836a94271af58c4604506cc142fe62b703f932815441aa313081f90589d34300ff66 languageName: node linkType: hard @@ -18865,8 +18865,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.10.10" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.42.1" - "@grafana/scenes-react": "npm:^6.42.1" + "@grafana/scenes": "npm:6.43.0--canary.1278.19036459049.0" + "@grafana/scenes-react": "npm:6.43.0--canary.1278.19036459049.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*"