Skip to content

Commit ca5cf5f

Browse files
authored
Dashboard datasource: Fix library panels not tracked in mixed queries (#112959)
* Dashboard datasource: Fix library panels not tracked in mixed queries * Remove unnecessary code and add unit tests * Add relevant comment
1 parent 06fb3fe commit ca5cf5f

File tree

2 files changed

+461
-46
lines changed

2 files changed

+461
-46
lines changed

public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx

Lines changed: 359 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ describe('DashboardDatasourceBehaviour', () => {
198198

199199
expect(spy).toHaveBeenCalledTimes(1);
200200
// since there is no previous request ID on dashboard load, the behaviour should not re-run queries
201-
expect(behaviour['prevRequestId']).toBeUndefined();
201+
expect(behaviour['prevRequestIds'].size).toBe(0);
202202
});
203203

204204
it('Should not re-run queries in behaviour on scene load', async () => {
@@ -242,7 +242,7 @@ describe('DashboardDatasourceBehaviour', () => {
242242

243243
expect(spy).toHaveBeenCalledTimes(1);
244244
// since there is no previous request ID on dashboard load, the behaviour should not re-run queries
245-
expect(behaviour['prevRequestId']).toBeUndefined();
245+
expect(behaviour['prevRequestIds'].size).toBe(0);
246246
});
247247

248248
it('Should exit behaviour early if not in a dashboard scene', async () => {
@@ -593,6 +593,363 @@ describe('DashboardDatasourceBehaviour', () => {
593593

594594
expect(spy).toHaveBeenCalled();
595595
});
596+
597+
it('Should re-run query when ANY source panel changes with multiple dashboardDS queries', async () => {
598+
jest.spyOn(console, 'error').mockImplementation();
599+
600+
// Create two source panels
601+
const sourcePanel1 = new VizPanel({
602+
title: 'Panel A',
603+
pluginId: 'table',
604+
key: 'panel-1',
605+
$data: new SceneDataTransformer({
606+
transformations: [],
607+
$data: new SceneQueryRunner({
608+
datasource: { uid: 'grafana' },
609+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
610+
}),
611+
}),
612+
});
613+
614+
const sourcePanel2 = new VizPanel({
615+
title: 'Panel B',
616+
pluginId: 'table',
617+
key: 'panel-2',
618+
$data: new SceneDataTransformer({
619+
transformations: [],
620+
$data: new SceneQueryRunner({
621+
datasource: { uid: 'grafana' },
622+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
623+
}),
624+
}),
625+
});
626+
627+
// Create a mixed DS panel that references BOTH source panels
628+
const mixedDSPanel = new VizPanel({
629+
title: 'Panel C - Mixed',
630+
pluginId: 'table',
631+
key: 'panel-3',
632+
$data: new SceneDataTransformer({
633+
transformations: [],
634+
$data: new SceneQueryRunner({
635+
datasource: { uid: MIXED_DATASOURCE_NAME },
636+
queries: [
637+
{
638+
datasource: { uid: SHARED_DASHBOARD_QUERY },
639+
refId: 'A',
640+
panelId: 1,
641+
},
642+
{
643+
datasource: { uid: SHARED_DASHBOARD_QUERY },
644+
refId: 'B',
645+
panelId: 2,
646+
},
647+
],
648+
$behaviors: [new DashboardDatasourceBehaviour({})],
649+
}),
650+
}),
651+
});
652+
653+
const scene = new DashboardScene({
654+
title: 'hello',
655+
uid: 'dash-1',
656+
meta: {
657+
canEdit: true,
658+
},
659+
body: DefaultGridLayoutManager.fromVizPanels([sourcePanel1, sourcePanel2, mixedDSPanel]),
660+
});
661+
662+
const sceneDeactivate = activateFullSceneTree(scene);
663+
664+
await new Promise((r) => setTimeout(r, 1));
665+
666+
const spy = jest
667+
.spyOn(mixedDSPanel.state.$data!.state.$data as SceneQueryRunner, 'runQueries')
668+
.mockImplementation();
669+
670+
// deactivate scene
671+
sceneDeactivate();
672+
673+
// Only change the SECOND source panel
674+
(sourcePanel2.state.$data!.state.$data as SceneQueryRunner).runQueries();
675+
676+
await new Promise((r) => setTimeout(r, 1));
677+
678+
// activate scene again
679+
activateFullSceneTree(scene);
680+
681+
// Should re-run because the second panel changed
682+
expect(spy).toHaveBeenCalled();
683+
});
684+
685+
it('Should track multiple dashboardDS queries independently', async () => {
686+
jest.spyOn(console, 'error').mockImplementation();
687+
688+
const sourcePanel1 = new VizPanel({
689+
title: 'Panel A',
690+
pluginId: 'table',
691+
key: 'panel-1',
692+
$data: new SceneDataTransformer({
693+
transformations: [],
694+
$data: new SceneQueryRunner({
695+
datasource: { uid: 'grafana' },
696+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
697+
}),
698+
}),
699+
});
700+
701+
const sourcePanel2 = new VizPanel({
702+
title: 'Panel B',
703+
pluginId: 'table',
704+
key: 'panel-2',
705+
$data: new SceneDataTransformer({
706+
transformations: [],
707+
$data: new SceneQueryRunner({
708+
datasource: { uid: 'grafana' },
709+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
710+
}),
711+
}),
712+
});
713+
714+
const mixedDSPanel = new VizPanel({
715+
title: 'Panel C - Mixed',
716+
pluginId: 'table',
717+
key: 'panel-3',
718+
$data: new SceneDataTransformer({
719+
transformations: [],
720+
$data: new SceneQueryRunner({
721+
datasource: { uid: MIXED_DATASOURCE_NAME },
722+
queries: [
723+
{
724+
datasource: { uid: SHARED_DASHBOARD_QUERY },
725+
refId: 'A',
726+
panelId: 1,
727+
},
728+
{
729+
datasource: { uid: SHARED_DASHBOARD_QUERY },
730+
refId: 'B',
731+
panelId: 2,
732+
},
733+
],
734+
$behaviors: [new DashboardDatasourceBehaviour({})],
735+
}),
736+
}),
737+
});
738+
739+
const scene = new DashboardScene({
740+
title: 'hello',
741+
uid: 'dash-1',
742+
meta: {
743+
canEdit: true,
744+
},
745+
body: DefaultGridLayoutManager.fromVizPanels([sourcePanel1, sourcePanel2, mixedDSPanel]),
746+
});
747+
748+
const sceneDeactivate = activateFullSceneTree(scene);
749+
750+
await new Promise((r) => setTimeout(r, 1));
751+
752+
const spy = jest
753+
.spyOn(mixedDSPanel.state.$data!.state.$data as SceneQueryRunner, 'runQueries')
754+
.mockImplementation();
755+
756+
// First cycle: change panel 1
757+
sceneDeactivate();
758+
(sourcePanel1.state.$data!.state.$data as SceneQueryRunner).runQueries();
759+
await new Promise((r) => setTimeout(r, 1));
760+
const deactivate2 = activateFullSceneTree(scene);
761+
expect(spy).toHaveBeenCalledTimes(1);
762+
763+
// Second cycle: change panel 2
764+
deactivate2();
765+
(sourcePanel2.state.$data!.state.$data as SceneQueryRunner).runQueries();
766+
await new Promise((r) => setTimeout(r, 1));
767+
activateFullSceneTree(scene);
768+
769+
// Should have been called again for panel 2
770+
expect(spy).toHaveBeenCalledTimes(2);
771+
});
772+
773+
it('Should handle multiple dashboardDS queries with library panels', async () => {
774+
jest.spyOn(console, 'error').mockImplementation();
775+
776+
const libPanelBehavior1 = new LibraryPanelBehavior({
777+
isLoaded: false,
778+
uid: 'lib-panel-1',
779+
name: 'Library Panel 1',
780+
_loadedPanel: undefined,
781+
});
782+
783+
const libPanelBehavior2 = new LibraryPanelBehavior({
784+
isLoaded: false,
785+
uid: 'lib-panel-2',
786+
name: 'Library Panel 2',
787+
_loadedPanel: undefined,
788+
});
789+
790+
const sourcePanel1 = new VizPanel({
791+
title: 'Panel A',
792+
pluginId: 'table',
793+
key: 'panel-1',
794+
$behaviors: [libPanelBehavior1],
795+
$data: new SceneQueryRunner({
796+
datasource: { uid: 'grafana' },
797+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
798+
}),
799+
});
800+
801+
const sourcePanel2 = new VizPanel({
802+
title: 'Panel B',
803+
pluginId: 'table',
804+
key: 'panel-2',
805+
$behaviors: [libPanelBehavior2],
806+
$data: new SceneQueryRunner({
807+
datasource: { uid: 'grafana' },
808+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
809+
}),
810+
});
811+
812+
const mixedDSPanel = new VizPanel({
813+
title: 'Panel C - Mixed',
814+
pluginId: 'table',
815+
key: 'panel-3',
816+
$data: new SceneQueryRunner({
817+
datasource: { uid: MIXED_DATASOURCE_NAME },
818+
queries: [
819+
{
820+
datasource: { uid: SHARED_DASHBOARD_QUERY },
821+
refId: 'A',
822+
panelId: 1,
823+
},
824+
{
825+
datasource: { uid: SHARED_DASHBOARD_QUERY },
826+
refId: 'B',
827+
panelId: 2,
828+
},
829+
],
830+
$behaviors: [new DashboardDatasourceBehaviour({})],
831+
}),
832+
});
833+
834+
const scene = new DashboardScene({
835+
title: 'hello',
836+
uid: 'dash-1',
837+
meta: {
838+
canEdit: true,
839+
},
840+
body: DefaultGridLayoutManager.fromVizPanels([sourcePanel1, sourcePanel2, mixedDSPanel]),
841+
});
842+
843+
activateFullSceneTree(scene);
844+
845+
const spy = jest.spyOn(mixedDSPanel.state.$data as SceneQueryRunner, 'runQueries');
846+
847+
await new Promise((r) => setTimeout(r, 1));
848+
849+
// Should not run queries until library panels are loaded
850+
expect(spy).not.toHaveBeenCalled();
851+
852+
// Load first library panel
853+
libPanelBehavior1.setState({
854+
isLoaded: true,
855+
uid: 'lib-panel-1',
856+
name: 'Library Panel 1',
857+
_loadedPanel: undefined,
858+
});
859+
860+
expect(spy).toHaveBeenCalledTimes(1);
861+
862+
// Load second library panel
863+
libPanelBehavior2.setState({
864+
isLoaded: true,
865+
uid: 'lib-panel-2',
866+
name: 'Library Panel 2',
867+
_loadedPanel: undefined,
868+
});
869+
870+
expect(spy).toHaveBeenCalledTimes(2);
871+
});
872+
873+
it('Should handle multiple queries with transformers on all source panels', async () => {
874+
jest.spyOn(console, 'error').mockImplementation();
875+
876+
const sourcePanel1 = new VizPanel({
877+
title: 'Panel A',
878+
pluginId: 'table',
879+
key: 'panel-1',
880+
$data: new SceneDataTransformer({
881+
transformations: [{ id: 'transformA', options: {} }],
882+
$data: new SceneQueryRunner({
883+
datasource: { uid: 'grafana' },
884+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
885+
}),
886+
}),
887+
});
888+
889+
const sourcePanel2 = new VizPanel({
890+
title: 'Panel B',
891+
pluginId: 'table',
892+
key: 'panel-2',
893+
$data: new SceneDataTransformer({
894+
transformations: [{ id: 'transformB', options: {} }],
895+
$data: new SceneQueryRunner({
896+
datasource: { uid: 'grafana' },
897+
queries: [{ refId: 'A', queryType: 'randomWalk' }],
898+
}),
899+
}),
900+
});
901+
902+
const mixedDSPanel = new VizPanel({
903+
title: 'Panel C - Mixed',
904+
pluginId: 'table',
905+
key: 'panel-3',
906+
$data: new SceneQueryRunner({
907+
datasource: { uid: MIXED_DATASOURCE_NAME },
908+
queries: [
909+
{
910+
datasource: { uid: SHARED_DASHBOARD_QUERY },
911+
refId: 'A',
912+
panelId: 1,
913+
},
914+
{
915+
datasource: { uid: SHARED_DASHBOARD_QUERY },
916+
refId: 'B',
917+
panelId: 2,
918+
},
919+
],
920+
$behaviors: [new DashboardDatasourceBehaviour({})],
921+
}),
922+
});
923+
924+
const scene = new DashboardScene({
925+
title: 'hello',
926+
uid: 'dash-1',
927+
meta: {
928+
canEdit: true,
929+
},
930+
body: DefaultGridLayoutManager.fromVizPanels([sourcePanel1, sourcePanel2, mixedDSPanel]),
931+
});
932+
933+
activateFullSceneTree(scene);
934+
935+
await new Promise((r) => setTimeout(r, 1));
936+
937+
const spy = jest.spyOn(mixedDSPanel.state.$data as SceneQueryRunner, 'runQueries').mockImplementation();
938+
939+
// Trigger transformer reprocessing on panel 1
940+
(sourcePanel1.state.$data as SceneDataTransformer).setState({
941+
data: { state: LoadingState.Done, series: [], timeRange: getDefaultTimeRange() },
942+
});
943+
944+
expect(spy).toHaveBeenCalledTimes(1);
945+
946+
// Trigger transformer reprocessing on panel 2
947+
(sourcePanel2.state.$data as SceneDataTransformer).setState({
948+
data: { state: LoadingState.Done, series: [], timeRange: getDefaultTimeRange() },
949+
});
950+
951+
expect(spy).toHaveBeenCalledTimes(2);
952+
});
596953
});
597954

598955
it('Should re-run query after transformations reprocess', async () => {

0 commit comments

Comments
 (0)