mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Rerender dashboard links on timerange change (#94570)
* fix * refactor * refactor
This commit is contained in:
parent
ef805f271e
commit
aaba5a43bd
@ -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 { selectors } from '@grafana/e2e-selectors';
|
||||||
import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes';
|
import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes';
|
||||||
|
|
||||||
import { DashboardControls, DashboardControlsState } from './DashboardControls';
|
import { DashboardControls, DashboardControlsState } from './DashboardControls';
|
||||||
import { DashboardScene } from './DashboardScene';
|
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('DashboardControls', () => {
|
||||||
describe('Given a standard scene', () => {
|
describe('Given a standard scene', () => {
|
||||||
it('should initialize with default values', () => {
|
it('should initialize with default values', () => {
|
||||||
const { controls: scene } = buildTestScene();
|
const scene = buildTestScene();
|
||||||
expect(scene.state.variableControls).toEqual([]);
|
expect(scene.state.variableControls).toEqual([]);
|
||||||
expect(scene.state.timePicker).toBeDefined();
|
expect(scene.state.timePicker).toBeDefined();
|
||||||
expect(scene.state.refreshPicker).toBeDefined();
|
expect(scene.state.refreshPicker).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return if time controls are hidden', () => {
|
it('should return if time controls are hidden', () => {
|
||||||
const { controls: scene } = buildTestScene({
|
const scene = buildTestScene({
|
||||||
hideTimeControls: false,
|
hideTimeControls: false,
|
||||||
hideVariableControls: false,
|
hideVariableControls: false,
|
||||||
hideLinksControls: false,
|
hideLinksControls: false,
|
||||||
@ -45,14 +31,14 @@ describe('DashboardControls', () => {
|
|||||||
|
|
||||||
describe('Component', () => {
|
describe('Component', () => {
|
||||||
it('should render', () => {
|
it('should render', () => {
|
||||||
const { controls: scene } = buildTestScene();
|
const scene = buildTestScene();
|
||||||
expect(() => {
|
expect(() => {
|
||||||
render(<scene.Component model={scene} />);
|
render(<scene.Component model={scene} />);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render visible controls', async () => {
|
it('should render visible controls', async () => {
|
||||||
const { controls: scene } = buildTestScene({
|
const scene = buildTestScene({
|
||||||
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()],
|
||||||
});
|
});
|
||||||
const renderer = render(<scene.Component model={scene} />);
|
const renderer = render(<scene.Component model={scene} />);
|
||||||
@ -65,7 +51,7 @@ describe('DashboardControls', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render with hidden controls', async () => {
|
it('should render with hidden controls', async () => {
|
||||||
const { controls: scene } = buildTestScene({
|
const scene = buildTestScene({
|
||||||
hideTimeControls: true,
|
hideTimeControls: true,
|
||||||
hideVariableControls: true,
|
hideVariableControls: true,
|
||||||
hideLinksControls: true,
|
hideLinksControls: true,
|
||||||
@ -79,13 +65,13 @@ describe('DashboardControls', () => {
|
|||||||
|
|
||||||
describe('UrlSync', () => {
|
describe('UrlSync', () => {
|
||||||
it('should return keys', () => {
|
it('should return keys', () => {
|
||||||
const { controls: scene } = buildTestScene();
|
const scene = buildTestScene();
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
expect(scene._urlSync.getKeys()).toEqual(['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks']);
|
expect(scene._urlSync.getKeys()).toEqual(['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return url state for hide flags', () => {
|
it('should not return url state for hide flags', () => {
|
||||||
const { controls: scene } = buildTestScene();
|
const scene = buildTestScene();
|
||||||
expect(scene.getUrlState()).toEqual({});
|
expect(scene.getUrlState()).toEqual({});
|
||||||
scene.setState({
|
scene.setState({
|
||||||
hideTimeControls: true,
|
hideTimeControls: true,
|
||||||
@ -96,7 +82,7 @@ describe('DashboardControls', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update from url', () => {
|
it('should update from url', () => {
|
||||||
const { controls: scene } = buildTestScene();
|
const scene = buildTestScene();
|
||||||
scene.updateFromUrl({
|
scene.updateFromUrl({
|
||||||
'_dash.hideTimePicker': 'true',
|
'_dash.hideTimePicker': 'true',
|
||||||
'_dash.hideVariables': 'true',
|
'_dash.hideVariables': 'true',
|
||||||
@ -116,7 +102,7 @@ describe('DashboardControls', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not override state if no new state comes from url', () => {
|
it('should not override state if no new state comes from url', () => {
|
||||||
const { controls: scene } = buildTestScene({
|
const scene = buildTestScene({
|
||||||
hideTimeControls: true,
|
hideTimeControls: true,
|
||||||
hideVariableControls: true,
|
hideVariableControls: true,
|
||||||
hideLinksControls: true,
|
hideLinksControls: true,
|
||||||
@ -128,7 +114,7 @@ describe('DashboardControls', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not call setState if no changes', () => {
|
it('should not call setState if no changes', () => {
|
||||||
const { controls: scene } = buildTestScene({
|
const scene = buildTestScene({
|
||||||
hideTimeControls: true,
|
hideTimeControls: true,
|
||||||
hideVariableControls: true,
|
hideVariableControls: true,
|
||||||
hideLinksControls: true,
|
hideLinksControls: true,
|
||||||
@ -144,34 +130,9 @@ describe('DashboardControls', () => {
|
|||||||
expect(setState).toHaveBeenCalledTimes(0);
|
expect(setState).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should update link hrefs when time range changes', () => {
|
|
||||||
const { controls, dashboard } = buildTestScene();
|
|
||||||
render(<controls.Component model={controls} />);
|
|
||||||
|
|
||||||
//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<DashboardControlsState>): {
|
function buildTestScene(state?: Partial<DashboardControlsState>): DashboardControls {
|
||||||
dashboard: DashboardScene;
|
|
||||||
controls: DashboardControls;
|
|
||||||
} {
|
|
||||||
const variable = new TextBoxVariable({
|
const variable = new TextBoxVariable({
|
||||||
name: 'A',
|
name: 'A',
|
||||||
label: 'A',
|
label: 'A',
|
||||||
@ -206,5 +167,5 @@ function buildTestScene(state?: Partial<DashboardControlsState>): {
|
|||||||
dashboard.activate();
|
dashboard.activate();
|
||||||
variable.activate();
|
variable.activate();
|
||||||
|
|
||||||
return { dashboard, controls: dashboard.state.controls as DashboardControls };
|
return dashboard.state.controls as DashboardControls;
|
||||||
}
|
}
|
||||||
|
@ -122,10 +122,9 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
|
|||||||
const { variableControls, refreshPicker, timePicker, hideTimeControls, hideVariableControls, hideLinksControls } =
|
const { variableControls, refreshPicker, timePicker, hideTimeControls, hideVariableControls, hideLinksControls } =
|
||||||
model.useState();
|
model.useState();
|
||||||
const dashboard = getDashboardSceneFor(model);
|
const dashboard = getDashboardSceneFor(model);
|
||||||
const { links, editPanel, $timeRange } = dashboard.useState();
|
const { links, editPanel } = dashboard.useState();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const showDebugger = location.search.includes('scene-debugger');
|
const showDebugger = location.search.includes('scene-debugger');
|
||||||
$timeRange!.useState();
|
|
||||||
|
|
||||||
if (!model.hasControls()) {
|
if (!model.hasControls()) {
|
||||||
return null;
|
return null;
|
||||||
@ -136,7 +135,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
|
|||||||
<Stack grow={1} wrap={'wrap'}>
|
<Stack grow={1} wrap={'wrap'}>
|
||||||
{!hideVariableControls && variableControls.map((c) => <c.Component model={c} key={c.state.key} />)}
|
{!hideVariableControls && variableControls.map((c) => <c.Component model={c} key={c.state.key} />)}
|
||||||
<Box grow={1} />
|
<Box grow={1} />
|
||||||
{!hideLinksControls && !editPanel && <DashboardLinksControls links={links} uid={dashboard.state.uid} />}
|
{!hideLinksControls && !editPanel && <DashboardLinksControls links={links} dashboard={dashboard} />}
|
||||||
{editPanel && <PanelEditControls panelEditor={editPanel} />}
|
{editPanel && <PanelEditControls panelEditor={editPanel} />}
|
||||||
</Stack>
|
</Stack>
|
||||||
{!hideTimeControls && (
|
{!hideTimeControls && (
|
||||||
|
@ -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(<controls.Component model={controls} />);
|
||||||
|
|
||||||
|
// // 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(<controls.Component model={controls} />);
|
||||||
|
|
||||||
|
//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 };
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { sanitizeUrl } from '@grafana/data/src/text/sanitize';
|
import { sanitizeUrl } from '@grafana/data/src/text/sanitize';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { sceneGraph } from '@grafana/scenes';
|
||||||
import { DashboardLink } from '@grafana/schema';
|
import { DashboardLink } from '@grafana/schema';
|
||||||
import { Tooltip } from '@grafana/ui';
|
import { Tooltip } from '@grafana/ui';
|
||||||
import {
|
import {
|
||||||
@ -10,12 +11,17 @@ import { getLinkSrv } from 'app/features/panel/panellinks/link_srv';
|
|||||||
|
|
||||||
import { LINK_ICON_MAP } from '../settings/links/utils';
|
import { LINK_ICON_MAP } from '../settings/links/utils';
|
||||||
|
|
||||||
|
import { DashboardScene } from './DashboardScene';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
links: DashboardLink[];
|
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) {
|
if (!links || !uid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user