From 0a27bfba5a9fbc18d28d61059862fbe0c496b4c7 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 11 Oct 2021 06:06:51 +0100 Subject: [PATCH] Dashboard: Fixes rendering of repeating panels (#39991) (#40259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dashboard: Fixes rendering of repeating panels * Chore: update after PR comments (cherry picked from commit d8e97fc024593c57dbea835973773265d2b5e437) Co-authored-by: Hugo Häggmark --- public/app/core/constants.ts | 1 + .../state/DashboardModel.repeat.test.ts | 105 ++++++++++++++++++ .../dashboard/state/DashboardModel.ts | 7 +- .../features/dashboard/state/utils.test.ts | 35 ++++++ public/app/features/dashboard/state/utils.ts | 17 +++ 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 public/app/features/dashboard/state/utils.test.ts create mode 100644 public/app/features/dashboard/state/utils.ts diff --git a/public/app/core/constants.ts b/public/app/core/constants.ts index 7a8ab490fe8..30a96fb558f 100644 --- a/public/app/core/constants.ts +++ b/public/app/core/constants.ts @@ -2,6 +2,7 @@ export const GRID_CELL_HEIGHT = 30; export const GRID_CELL_VMARGIN = 8; export const GRID_COLUMN_COUNT = 24; export const REPEAT_DIR_VERTICAL = 'v'; +export const REPEAT_DIR_HORIZONTAL = 'h'; export const DEFAULT_PANEL_SPAN = 4; export const DEFAULT_ROW_HEIGHT = 250; diff --git a/public/app/features/dashboard/state/DashboardModel.repeat.test.ts b/public/app/features/dashboard/state/DashboardModel.repeat.test.ts index 3bf4355540c..638723e2a03 100644 --- a/public/app/features/dashboard/state/DashboardModel.repeat.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.repeat.test.ts @@ -682,6 +682,111 @@ describe('given dashboard with row and panel repeat', () => { }); }); +// fix for https://github.com/grafana/grafana/issues/38805 +describe('given dashboard with row and repeats on same row', () => { + it('should set correct gridPos when row is expanding', () => { + const ROW1 = 1; + const GAUGE1 = 2; + const REPEAT1 = 3; + const GAUGE2 = 4; + const REPEAT2 = 5; + const GAUGE3 = 6; + const dashboardJSON = { + panels: [ + { + collapsed: true, + datasource: null, + gridPos: { h: 1, w: 24, x: 0, y: 0 }, + id: ROW1, + panels: [ + { gridPos: { h: 5, w: 4, x: 0, y: 1 }, id: GAUGE1, type: 'gauge' }, + { + gridPos: { h: 5, w: 4, x: 4, y: 1 }, + id: REPEAT1, + repeat: 'abc', + repeatDirection: 'v', + type: 'gauge', + }, + { gridPos: { h: 5, w: 4, x: 8, y: 1 }, id: GAUGE2, type: 'gauge' }, + { + gridPos: { h: 5, w: 4, x: 12, y: 1 }, + id: REPEAT2, + repeat: 'abc', + repeatDirection: 'v', + type: 'gauge', + }, + { gridPos: { h: 5, w: 4, x: 16, y: 1 }, id: GAUGE3, type: 'gauge' }, + ], + title: 'Row title', + type: 'row', + }, + ], + templating: { + list: [ + { + allValue: null, + current: { selected: true, text: ['All'], value: ['$__all'] }, + includeAll: true, + name: 'abc', + options: [ + { selected: true, text: 'All', value: '$__all' }, + { selected: false, text: 'a', value: 'a' }, + { selected: false, text: 'b', value: 'b' }, + { selected: false, text: 'c', value: 'c' }, + { selected: false, text: 'd', value: 'd' }, + { selected: false, text: 'e', value: 'e' }, + { selected: false, text: 'f', value: 'f' }, + { selected: false, text: 'g', value: 'g' }, + ], + type: 'custom', + }, + ], + }, + }; + const dashboard = getDashboardModel(dashboardJSON); + + // toggle row + dashboard.toggleRow(dashboard.panels[0]); + + // correct number of panels + expect(dashboard.panels.length).toBe(18); + + // check row + const rowPanel = dashboard.panels.find((p) => p.id === ROW1); + expect(rowPanel?.gridPos).toEqual({ x: 0, y: 0, w: 24, h: 1 }); + + // check the gridPos of all the top level panels that are next to each other + const firstGauge = dashboard.panels.find((p) => p.id === GAUGE1); + const secondGauge = dashboard.panels.find((p) => p.id === GAUGE2); + const thirdGauge = dashboard.panels.find((p) => p.id === GAUGE3); + const firstVerticalRepeatingGauge = dashboard.panels.find((p) => p.id === REPEAT1); + const secondVerticalRepeatingGauge = dashboard.panels.find((p) => p.id === REPEAT2); + expect(firstGauge?.gridPos).toEqual({ x: 0, y: 1, w: 4, h: 5 }); + expect(secondGauge?.gridPos).toEqual({ x: 8, y: 1, w: 4, h: 5 }); + expect(thirdGauge?.gridPos).toEqual({ x: 16, y: 1, w: 4, h: 5 }); + expect(firstVerticalRepeatingGauge?.gridPos).toEqual({ x: 4, y: 1, w: 4, h: 5 }); + expect(secondVerticalRepeatingGauge?.gridPos).toEqual({ x: 12, y: 1, w: 4, h: 5 }); + + // check the gridPos of all first vertical repeats children + const { x, h, w } = firstVerticalRepeatingGauge!.gridPos; + expect(dashboard.panels[6].gridPos).toEqual({ x, y: 6, w, h }); + expect(dashboard.panels[8].gridPos).toEqual({ x, y: 11, w, h }); + expect(dashboard.panels[10].gridPos).toEqual({ x, y: 16, w, h }); + expect(dashboard.panels[12].gridPos).toEqual({ x, y: 21, w, h }); + expect(dashboard.panels[14].gridPos).toEqual({ x, y: 26, w, h }); + expect(dashboard.panels[16].gridPos).toEqual({ x, y: 31, w, h }); + + // check the gridPos of all second vertical repeats children + const { x: x2, h: h2, w: w2 } = secondVerticalRepeatingGauge!.gridPos; + expect(dashboard.panels[7].gridPos).toEqual({ x: x2, y: 6, w: w2, h: h2 }); + expect(dashboard.panels[9].gridPos).toEqual({ x: x2, y: 11, w: w2, h: h2 }); + expect(dashboard.panels[11].gridPos).toEqual({ x: x2, y: 16, w: w2, h: h2 }); + expect(dashboard.panels[13].gridPos).toEqual({ x: x2, y: 21, w: w2, h: h2 }); + expect(dashboard.panels[15].gridPos).toEqual({ x: x2, y: 26, w: w2, h: h2 }); + expect(dashboard.panels[17].gridPos).toEqual({ x: x2, y: 31, w: w2, h: h2 }); + }); +}); + describe('given panel is in view mode', () => { let dashboard: any; diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index bf57ab6a297..e49a875eab2 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -31,10 +31,10 @@ import { DateTimeInput, EventBusExtended, EventBusSrv, + PanelModel as IPanelModel, TimeRange, TimeZone, UrlQueryValue, - PanelModel as IPanelModel, } from '@grafana/data'; import { CoreEvents, DashboardMeta, KioskMode } from 'app/types'; import { GetVariables, getVariables } from 'app/features/variables/state/selectors'; @@ -45,6 +45,7 @@ import { isAllVariable } from '../../variables/utils'; import { DashboardPanelsChangedEvent, RefreshEvent, RenderEvent, TimeRangeUpdatedEvent } from 'app/types/events'; import { getTimeSrv } from '../services/TimeSrv'; import { mergePanels, PanelMergeInfo } from '../utils/panelMerge'; +import { isOnTheSameGridRow } from './utils'; export interface CloneOptions { saveVariables?: boolean; @@ -703,6 +704,10 @@ export class DashboardModel { if (yOffset > 0) { const panelBelowIndex = panelIndex + selectedOptions.length; for (let i = panelBelowIndex; i < this.panels.length; i++) { + if (isOnTheSameGridRow(panel, this.panels[i])) { + continue; + } + this.panels[i].gridPos.y += yOffset; } } diff --git a/public/app/features/dashboard/state/utils.test.ts b/public/app/features/dashboard/state/utils.test.ts new file mode 100644 index 00000000000..d6f35a986d5 --- /dev/null +++ b/public/app/features/dashboard/state/utils.test.ts @@ -0,0 +1,35 @@ +import { PanelModel } from './PanelModel'; +import { isOnTheSameGridRow } from './utils'; +import { REPEAT_DIR_HORIZONTAL } from '../../../core/constants'; + +describe('isOnTheSameGridRow', () => { + describe('when source panel is next to a panel', () => { + it('then it should return true', () => { + const sourcePanel: PanelModel = ({ gridPos: { x: 0, y: 1, w: 4, h: 4 } } as unknown) as PanelModel; + const otherPanel: PanelModel = ({ gridPos: { x: 4, y: 1, w: 4, h: 4 } } as unknown) as PanelModel; + + expect(isOnTheSameGridRow(sourcePanel, otherPanel)).toBe(true); + }); + }); + + describe('when source panel is not next to a panel', () => { + it('then it should return false', () => { + const sourcePanel: PanelModel = ({ gridPos: { x: 0, y: 1, w: 4, h: 4 } } as unknown) as PanelModel; + const otherPanel: PanelModel = ({ gridPos: { x: 4, y: 5, w: 4, h: 4 } } as unknown) as PanelModel; + + expect(isOnTheSameGridRow(sourcePanel, otherPanel)).toBe(false); + }); + }); + + describe('when source panel is repeated horizontally', () => { + it('then it should return false', () => { + const sourcePanel: PanelModel = ({ + gridPos: { x: 0, y: 1, w: 4, h: 4 }, + repeatDirection: REPEAT_DIR_HORIZONTAL, + } as unknown) as PanelModel; + const otherPanel: PanelModel = ({ gridPos: { x: 4, y: 1, w: 4, h: 4 } } as unknown) as PanelModel; + + expect(isOnTheSameGridRow(sourcePanel, otherPanel)).toBe(false); + }); + }); +}); diff --git a/public/app/features/dashboard/state/utils.ts b/public/app/features/dashboard/state/utils.ts new file mode 100644 index 00000000000..8742b1a1851 --- /dev/null +++ b/public/app/features/dashboard/state/utils.ts @@ -0,0 +1,17 @@ +import { PanelModel } from './PanelModel'; +import { REPEAT_DIR_HORIZONTAL } from '../../../core/constants'; + +export function isOnTheSameGridRow(sourcePanel: PanelModel, otherPanel: PanelModel): boolean { + if (sourcePanel.repeatDirection === REPEAT_DIR_HORIZONTAL) { + return false; + } + + if ( + otherPanel.gridPos.x >= sourcePanel.gridPos.x + sourcePanel.gridPos.w && + otherPanel.gridPos.y === sourcePanel.gridPos.y + ) { + return true; + } + + return false; +}