diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx index dc4c492bf92..c01808f411e 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx @@ -14,17 +14,56 @@ describe('DashboardControls', () => { expect(scene.state.refreshPicker).toBeDefined(); }); - it('should return if time controls are hidden', () => { - const scene = buildTestScene({ - hideTimeControls: false, - hideVariableControls: false, - hideLinksControls: false, + describe('.hasControls()', () => { + it('should return TRUE if any of the controls are available', () => { + const scene = buildTestScene({ + hideTimeControls: false, + hideVariableControls: false, + hideLinksControls: false, + hideDashboardControls: false, + }); + + // All controls visible + expect(scene.hasControls()).toBeTruthy(); + + // Hiding time controls + scene.setState({ + hideTimeControls: true, + hideVariableControls: false, + hideLinksControls: false, + hideDashboardControls: false, + }); + expect(scene.hasControls()).toBeTruthy(); + + // Hide variable controls as well + scene.setState({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: false, + hideDashboardControls: false, + }); + expect(scene.hasControls()).toBeTruthy(); + + // Hide link controls as well + scene.setState({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + hideDashboardControls: false, + }); + expect(scene.hasControls()).toBeTruthy(); + }); + + it('should return FALSE if no controls are available', () => { + const scene = buildTestScene({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + hideDashboardControls: true, + }); + + expect(scene.hasControls()).toBeFalsy(); }); - expect(scene.hasControls()).toBeTruthy(); - scene.setState({ hideTimeControls: true }); - expect(scene.hasControls()).toBeTruthy(); - scene.setState({ hideVariableControls: true, hideLinksControls: true }); - expect(scene.hasControls()).toBeFalsy(); }); }); @@ -52,10 +91,11 @@ describe('DashboardControls', () => { hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, + hideDashboardControls: true, }); const renderer = render(); - expect(await renderer.queryByTestId(selectors.pages.Dashboard.Controls)).not.toBeInTheDocument(); + expect(renderer.queryByTestId(selectors.pages.Dashboard.Controls)).not.toBeInTheDocument(); }); }); @@ -63,7 +103,12 @@ describe('DashboardControls', () => { it('should return keys', () => { const scene = buildTestScene(); // @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', + '_dash.hideDashboardControls', + ]); }); it('should not return url state for hide flags', () => { @@ -73,6 +118,7 @@ describe('DashboardControls', () => { hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, + hideDashboardControls: true, }); expect(scene.getUrlState()).toEqual({}); }); @@ -83,18 +129,22 @@ describe('DashboardControls', () => { '_dash.hideTimePicker': 'true', '_dash.hideVariables': 'true', '_dash.hideLinks': 'true', + '_dash.hideDashboardControls': 'true', }); expect(scene.state.hideTimeControls).toBeTruthy(); expect(scene.state.hideVariableControls).toBeTruthy(); expect(scene.state.hideLinksControls).toBeTruthy(); + expect(scene.state.hideDashboardControls).toBeTruthy(); scene.updateFromUrl({ '_dash.hideTimePicker': '', '_dash.hideVariables': '', '_dash.hideLinks': '', + '_dash.hideDashboardControls': '', }); expect(scene.state.hideTimeControls).toBeTruthy(); expect(scene.state.hideVariableControls).toBeTruthy(); expect(scene.state.hideLinksControls).toBeTruthy(); + expect(scene.state.hideDashboardControls).toBeTruthy(); }); it('should not override state if no new state comes from url', () => { @@ -102,11 +152,13 @@ describe('DashboardControls', () => { hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, + hideDashboardControls: true, }); scene.updateFromUrl({}); expect(scene.state.hideTimeControls).toBeTruthy(); expect(scene.state.hideVariableControls).toBeTruthy(); expect(scene.state.hideLinksControls).toBeTruthy(); + expect(scene.state.hideDashboardControls).toBeTruthy(); }); it('should not call setState if no changes', () => { @@ -114,6 +166,7 @@ describe('DashboardControls', () => { hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, + hideDashboardControls: true, }); const setState = jest.spyOn(scene, 'setState'); @@ -121,6 +174,7 @@ describe('DashboardControls', () => { '_dash.hideTimePicker': 'true', '_dash.hideVariables': 'true', '_dash.hideLinks': 'true', + '_dash.hideDashboardControls': 'true', }); expect(setState).toHaveBeenCalledTimes(0); @@ -151,6 +205,19 @@ function buildTestScene(state?: Partial): DashboardContr targetBlank: false, tooltip: 'Link', }, + { + title: 'Link (dashboard controls)', + url: 'http://localhost:3000/$A', + type: 'link', + asDropdown: false, + icon: '', + includeVars: true, + keepTime: true, + tags: [], + targetBlank: false, + tooltip: 'Link', + placement: 'inControlsMenu', + }, ], $variables: new SceneVariableSet({ variables: [variable], diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index f553cdb77b3..1a22dd8a2c0 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -31,6 +31,8 @@ export interface DashboardControlsState extends SceneObjectState { hideTimeControls?: boolean; hideVariableControls?: boolean; hideLinksControls?: boolean; + // Hides the dashbaord-controls dropdown menu + hideDashboardControls?: boolean; } export class DashboardControls extends SceneObjectBase { @@ -41,7 +43,7 @@ export class DashboardControls extends SceneObjectBase { }); protected _urlSync = new SceneObjectUrlSyncConfig(this, { - keys: ['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks'], + keys: ['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks', '_dash.hideDashboardControls'], }); /** @@ -53,7 +55,7 @@ export class DashboardControls extends SceneObjectBase { } updateFromUrl(values: SceneObjectUrlValues) { - const { hideTimeControls, hideVariableControls, hideLinksControls } = this.state; + const { hideTimeControls, hideVariableControls, hideLinksControls, hideDashboardControls } = this.state; const isEnabledViaUrl = (key: string) => values[key] === 'true' || values[key] === ''; // Only allow hiding, never "unhiding" from url @@ -70,6 +72,10 @@ export class DashboardControls extends SceneObjectBase { if (!hideLinksControls && isEnabledViaUrl('_dash.hideLinks')) { this.setState({ hideLinksControls: true }); } + + if (!hideDashboardControls && isEnabledViaUrl('_dash.hideDashboardControls')) { + this.setState({ hideDashboardControls: true }); + } } public constructor(state: Partial) { @@ -104,6 +110,18 @@ export class DashboardControls extends SceneObjectBase { } } + // Dashboard controls is a separate dropdown menu at the top-right of the controls + public hasDashboardControls(): boolean { + const dashboard = getDashboardSceneFor(this); + const { links } = dashboard.state; + const hasControlMenuVariables = sceneGraph + .getVariables(dashboard) + ?.state.variables.some((v) => v.state.showInControlsMenu === true); + const hasControlMenuLinks = links.some((link) => link.placement === 'inControlsMenu'); + + return hasControlMenuVariables || hasControlMenuLinks; + } + public hasControls(): boolean { const hasVariables = sceneGraph .getVariables(this) @@ -113,22 +131,25 @@ export class DashboardControls extends SceneObjectBase { const hideLinks = this.state.hideLinksControls || !hasLinks; const hideVariables = this.state.hideVariableControls || (!hasAnnotations && !hasVariables); const hideTimePicker = this.state.hideTimeControls; + const hideDashboardControls = this.state.hideDashboardControls || !this.hasDashboardControls(); - return !(hideVariables && hideLinks && hideTimePicker); + return !(hideVariables && hideLinks && hideTimePicker && hideDashboardControls); } } function DashboardControlsRenderer({ model }: SceneComponentProps) { - const { refreshPicker, timePicker, hideTimeControls, hideVariableControls, hideLinksControls } = model.useState(); + const { + refreshPicker, + timePicker, + hideTimeControls, + hideVariableControls, + hideLinksControls, + hideDashboardControls, + } = model.useState(); const dashboard = getDashboardSceneFor(model); const { links, editPanel } = dashboard.useState(); const styles = useStyles2(getStyles); const showDebugger = window.location.search.includes('scene-debugger'); - const hasControlMenuVariables = sceneGraph - .getVariables(dashboard) - .useState() - .variables.some((v) => v.state.showInControlsMenu === true); - const hasControlMenuLinks = links.some((link) => link.placement === 'inControlsMenu'); if (!model.hasControls()) { // To still have spacing when no controls are rendered @@ -157,7 +178,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps )} - {(hasControlMenuVariables || hasControlMenuLinks) && ( + {!hideDashboardControls && model.hasDashboardControls() && (