From aaba5a43bd2aa3d29955767b678ed6a02a1f7100 Mon Sep 17 00:00:00 2001
From: Victor Marin <36818606+mdvictor@users.noreply.github.com>
Date: Tue, 15 Oct 2024 12:39:36 +0300
Subject: [PATCH] DashboardScene: Rerender dashboard links on timerange change
(#94570)
* fix
* refactor
* refactor
---
.../scene/DashboardControls.test.tsx | 65 +++---------
.../scene/DashboardControls.tsx | 5 +-
.../scene/DashboardLinksControls.test.tsx | 100 ++++++++++++++++++
.../scene/DashboardLinksControls.tsx | 10 +-
4 files changed, 123 insertions(+), 57 deletions(-)
create mode 100644 public/app/features/dashboard-scene/scene/DashboardLinksControls.test.tsx
diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx
index 34c5f3fe057..878f6cc5e87 100644
--- a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx
@@ -1,36 +1,22 @@
-import { act, render } from '@testing-library/react';
+import { render } from '@testing-library/react';
-import { toUtc } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes';
import { DashboardControls, DashboardControlsState } from './DashboardControls';
import { DashboardScene } from './DashboardScene';
-const mockGetAnchorInfo = jest.fn((link) => ({
- href: `/dashboard/${link.title}`,
- title: link.title,
- tooltip: link.tooltip || null,
-}));
-
-// Mock the getLinkSrv function
-jest.mock('app/features/panel/panellinks/link_srv', () => ({
- getLinkSrv: jest.fn(() => ({
- getAnchorInfo: mockGetAnchorInfo,
- })),
-}));
-
describe('DashboardControls', () => {
describe('Given a standard scene', () => {
it('should initialize with default values', () => {
- const { controls: scene } = buildTestScene();
+ const scene = buildTestScene();
expect(scene.state.variableControls).toEqual([]);
expect(scene.state.timePicker).toBeDefined();
expect(scene.state.refreshPicker).toBeDefined();
});
it('should return if time controls are hidden', () => {
- const { controls: scene } = buildTestScene({
+ const scene = buildTestScene({
hideTimeControls: false,
hideVariableControls: false,
hideLinksControls: false,
@@ -45,14 +31,14 @@ describe('DashboardControls', () => {
describe('Component', () => {
it('should render', () => {
- const { controls: scene } = buildTestScene();
+ const scene = buildTestScene();
expect(() => {
render();
}).not.toThrow();
});
it('should render visible controls', async () => {
- const { controls: scene } = buildTestScene({
+ const scene = buildTestScene({
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
});
const renderer = render();
@@ -65,7 +51,7 @@ describe('DashboardControls', () => {
});
it('should render with hidden controls', async () => {
- const { controls: scene } = buildTestScene({
+ const scene = buildTestScene({
hideTimeControls: true,
hideVariableControls: true,
hideLinksControls: true,
@@ -79,13 +65,13 @@ describe('DashboardControls', () => {
describe('UrlSync', () => {
it('should return keys', () => {
- const { controls: scene } = buildTestScene();
+ const scene = buildTestScene();
// @ts-expect-error
expect(scene._urlSync.getKeys()).toEqual(['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks']);
});
it('should not return url state for hide flags', () => {
- const { controls: scene } = buildTestScene();
+ const scene = buildTestScene();
expect(scene.getUrlState()).toEqual({});
scene.setState({
hideTimeControls: true,
@@ -96,7 +82,7 @@ describe('DashboardControls', () => {
});
it('should update from url', () => {
- const { controls: scene } = buildTestScene();
+ const scene = buildTestScene();
scene.updateFromUrl({
'_dash.hideTimePicker': 'true',
'_dash.hideVariables': 'true',
@@ -116,7 +102,7 @@ describe('DashboardControls', () => {
});
it('should not override state if no new state comes from url', () => {
- const { controls: scene } = buildTestScene({
+ const scene = buildTestScene({
hideTimeControls: true,
hideVariableControls: true,
hideLinksControls: true,
@@ -128,7 +114,7 @@ describe('DashboardControls', () => {
});
it('should not call setState if no changes', () => {
- const { controls: scene } = buildTestScene({
+ const scene = buildTestScene({
hideTimeControls: true,
hideVariableControls: true,
hideLinksControls: true,
@@ -144,34 +130,9 @@ describe('DashboardControls', () => {
expect(setState).toHaveBeenCalledTimes(0);
});
});
-
- it('Should update link hrefs when time range changes', () => {
- const { controls, dashboard } = buildTestScene();
- render();
-
- //clear initial calls to getAnchorInfo
- mockGetAnchorInfo.mockClear();
-
- act(() => {
- // Update time range
- dashboard.state.$timeRange?.setState({
- value: {
- from: toUtc('2021-01-01'),
- to: toUtc('2021-01-02'),
- raw: { from: toUtc('2020-01-01'), to: toUtc('2020-01-02') },
- },
- });
- });
-
- //expect getAnchorInfo to be called after time range change
- expect(mockGetAnchorInfo).toHaveBeenCalledTimes(1);
- });
});
-function buildTestScene(state?: Partial): {
- dashboard: DashboardScene;
- controls: DashboardControls;
-} {
+function buildTestScene(state?: Partial): DashboardControls {
const variable = new TextBoxVariable({
name: 'A',
label: 'A',
@@ -206,5 +167,5 @@ function buildTestScene(state?: Partial): {
dashboard.activate();
variable.activate();
- return { dashboard, controls: dashboard.state.controls as DashboardControls };
+ return dashboard.state.controls as DashboardControls;
}
diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx
index 41e125957a5..78fbc64afd8 100644
--- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx
@@ -122,10 +122,9 @@ function DashboardControlsRenderer({ model }: SceneComponentProps
{!hideVariableControls && variableControls.map((c) => )}
- {!hideLinksControls && !editPanel && }
+ {!hideLinksControls && !editPanel && }
{editPanel && }
{!hideTimeControls && (
diff --git a/public/app/features/dashboard-scene/scene/DashboardLinksControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardLinksControls.test.tsx
new file mode 100644
index 00000000000..d6d89a2e63f
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/DashboardLinksControls.test.tsx
@@ -0,0 +1,100 @@
+import { act, render } from '@testing-library/react';
+
+import { toUtc } from '@grafana/data';
+import { selectors } from '@grafana/e2e-selectors';
+import { SceneTimeRange } from '@grafana/scenes';
+
+import { DashboardControls } from './DashboardControls';
+import { DashboardScene } from './DashboardScene';
+
+const mockGetAnchorInfo = jest.fn((link) => ({
+ href: `/dashboard/${link.title}`,
+ title: link.title,
+ tooltip: link.tooltip || null,
+}));
+
+// Mock the getLinkSrv function
+jest.mock('app/features/panel/panellinks/link_srv', () => ({
+ getLinkSrv: jest.fn(() => ({
+ getAnchorInfo: mockGetAnchorInfo,
+ })),
+}));
+
+describe('DashboardLinksControls', () => {
+ it('renders dashboard links correctly', () => {
+ const { controls } = buildTestScene();
+ const renderer = render();
+
+ // // Expect two dashboard link containers to be rendered
+ const linkContainers = renderer.getAllByTestId(selectors.components.DashboardLinks.container);
+ expect(linkContainers).toHaveLength(2);
+
+ // Check link titles and hrefs
+ const links = renderer.getAllByTestId(selectors.components.DashboardLinks.link);
+ expect(links[0]).toHaveTextContent('Link 1');
+ expect(links[1]).toHaveTextContent('Link 2');
+ });
+
+ it('updates link hrefs when time range changes', () => {
+ const { controls, dashboard } = buildTestScene();
+ render();
+
+ //clear initial calls to getAnchorInfo
+ mockGetAnchorInfo.mockClear();
+
+ act(() => {
+ // Update time range
+ dashboard.state.$timeRange?.setState({
+ value: {
+ from: toUtc('2021-01-01'),
+ to: toUtc('2021-01-02'),
+ raw: { from: toUtc('2020-01-01'), to: toUtc('2020-01-02') },
+ },
+ });
+ });
+
+ //expect getAnchorInfo to be called twice, once for each link, after time range change
+ expect(mockGetAnchorInfo).toHaveBeenCalledTimes(2);
+ });
+});
+
+function buildTestScene(): { controls: DashboardControls; dashboard: DashboardScene } {
+ const dashboard = new DashboardScene({
+ uid: 'A',
+ links: [
+ {
+ title: 'Link 1',
+ url: 'http://localhost:3000/$A',
+ type: 'link',
+ asDropdown: false,
+ icon: '',
+ includeVars: true,
+ keepTime: true,
+ tags: [],
+ targetBlank: false,
+ tooltip: 'Link 1',
+ },
+ {
+ title: 'Link 2',
+ url: 'http://localhost:3000/$A',
+ type: 'link',
+ asDropdown: false,
+ icon: '',
+ includeVars: true,
+ keepTime: true,
+ tags: [],
+ targetBlank: false,
+ tooltip: 'Link 2',
+ },
+ ],
+ controls: new DashboardControls({}),
+ $timeRange: new SceneTimeRange({
+ from: 'now-1',
+ to: 'now',
+ }),
+ });
+
+ dashboard.activate();
+
+ return { controls: dashboard.state.controls as DashboardControls, dashboard };
+}
diff --git a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx
index 0337580526a..72a046fc6e5 100644
--- a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx
+++ b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx
@@ -1,5 +1,6 @@
import { sanitizeUrl } from '@grafana/data/src/text/sanitize';
import { selectors } from '@grafana/e2e-selectors';
+import { sceneGraph } from '@grafana/scenes';
import { DashboardLink } from '@grafana/schema';
import { Tooltip } from '@grafana/ui';
import {
@@ -10,12 +11,17 @@ import { getLinkSrv } from 'app/features/panel/panellinks/link_srv';
import { LINK_ICON_MAP } from '../settings/links/utils';
+import { DashboardScene } from './DashboardScene';
+
export interface Props {
links: DashboardLink[];
- uid?: string;
+ dashboard: DashboardScene;
}
-export function DashboardLinksControls({ links, uid }: Props) {
+export function DashboardLinksControls({ links, dashboard }: Props) {
+ sceneGraph.getTimeRange(dashboard).useState();
+ const uid = dashboard.state.uid;
+
if (!links || !uid) {
return null;
}