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; }