From 713e624790e1cee7a829b102dd654ad290eec726 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Wed, 4 May 2022 09:39:41 +0100 Subject: [PATCH] DashboardModel: Tidy up some of the older code (#48355) --- .../dashboard/state/DashboardMigrator.test.ts | 8 +- .../dashboard/state/DashboardMigrator.ts | 9 +- .../dashboard/state/DashboardModel.test.ts | 2 +- .../dashboard/state/DashboardModel.ts | 462 +++++++----------- .../features/dashboard/state/PanelModel.ts | 2 +- .../features/dashboard/state/utils.test.ts | 8 +- 6 files changed, 184 insertions(+), 307 deletions(-) diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index b53a4088208..a2269ab56df 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -1449,7 +1449,7 @@ describe('DashboardModel', () => { }); it('should ignore fieldConfig.defaults', () => { - expect(model.panels[0].panels[0].fieldConfig.defaults).toEqual(undefined); + expect(model.panels[0].panels?.[0].fieldConfig.defaults).toEqual(undefined); }); }); @@ -1749,8 +1749,8 @@ describe('DashboardModel', () => { }, ], }); - panel1Targets = nestedModel.panels[0].panels[0].targets; - panel2Targets = nestedModel.panels[0].panels[1].targets; + panel1Targets = nestedModel.panels[0].panels?.[0].targets; + panel2Targets = nestedModel.panels[0].panels?.[1].targets; }); it('multiple stats query should have been split into one query per stat', () => { @@ -1851,7 +1851,7 @@ describe('DashboardModel', () => { }); it('should update datasources in panels collapsed rows', () => { - expect(model.panels[3].panels[0].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); + expect(model.panels[3].panels?.[0].datasource).toEqual({ type: 'prometheus', uid: 'prom-uid' }); }); }); diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 57279169b29..79b747773e9 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -784,9 +784,10 @@ export class DashboardMigrator { for (j = 0; j < this.dashboard.panels.length; j++) { for (k = 0; k < panelUpgrades.length; k++) { this.dashboard.panels[j] = panelUpgrades[k].call(this, this.dashboard.panels[j]); - if (this.dashboard.panels[j].panels) { - for (n = 0; n < this.dashboard.panels[j].panels.length; n++) { - this.dashboard.panels[j].panels[n] = panelUpgrades[k].call(this, this.dashboard.panels[j].panels[n]); + const rowPanels = this.dashboard.panels[j].panels; + if (rowPanels) { + for (n = 0; n < rowPanels.length; n++) { + rowPanels[n] = panelUpgrades[k].call(this, rowPanels[n]); } } } @@ -895,7 +896,7 @@ export class DashboardMigrator { delete panel.span; if (rowPanelModel && rowPanel.collapsed) { - rowPanelModel.panels.push(panel); + rowPanelModel.panels?.push(panel); } else { this.dashboard.panels.push(new PanelModel(panel)); } diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index f5d494a780c..b14fc23d754 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -370,7 +370,7 @@ describe('DashboardModel', () => { it('should remove panels and put them inside collapsed row', () => { expect(dashboard.panels.length).toBe(3); - expect(dashboard.panels[1].panels.length).toBe(2); + expect(dashboard.panels[1].panels?.length).toBe(2); }); describe('and when removing row and its panels', () => { diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 1c99bb5a33a..7398d9cd803 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -1,17 +1,4 @@ -import { - cloneDeep, - defaults as _defaults, - each, - filter, - find, - findIndex, - indexOf, - isEqual, - map, - maxBy, - pull, - some, -} from 'lodash'; +import { cloneDeep, defaults as _defaults, filter, indexOf, isEqual, map, maxBy, pull } from 'lodash'; import { Subscription } from 'rxjs'; import { @@ -194,14 +181,7 @@ export class DashboardModel implements TimeModel { } addBuiltInAnnotationQuery() { - let found = false; - for (const item of this.annotations.list) { - if (item.builtIn === 1) { - found = true; - break; - } - } - + const found = this.annotations.list.some((item) => item.builtIn === 1); if (found) { return; } @@ -267,9 +247,7 @@ export class DashboardModel implements TimeModel { // sort by keys copy = sortedDeepCloneWithoutNulls(copy); - copy.getVariables = () => { - return copy.templating.list; - }; + copy.getVariables = () => copy.templating.list; return copy; } @@ -296,24 +274,11 @@ export class DashboardModel implements TimeModel { private getPanelSaveModels() { return this.panels - .filter((panel: PanelModel) => { - if (this.isSnapshotTruthy()) { - return true; - } - if (panel.type === 'add-panel') { - return false; - } - // skip repeated panels in the saved model - if (panel.repeatPanelId) { - return false; - } - // skip repeated rows in the saved model - if (panel.repeatedByRow) { - return false; - } - return true; - }) - .map((panel: PanelModel) => { + .filter( + (panel) => + this.isSnapshotTruthy() || !(panel.type === 'add-panel' || panel.repeatPanelId || panel.repeatedByRow) + ) + .map((panel) => { // If we save while editing we should include the panel in edit mode instead of the // unmodified source panel if (this.panelInEdit && this.panelInEdit.id === panel.id) { @@ -358,18 +323,19 @@ export class DashboardModel implements TimeModel { }; if (!defaults.saveVariables) { - for (let i = 0; i < copy.templating.list.length; i++) { - const current = copy.templating.list[i]; - const original: any = find(originalVariables, { name: current.name, type: current.type }); + for (const current of copy.templating.list) { + const original = originalVariables.find( + ({ name, type }: any) => name === current.name && type === current.type + ); if (!original) { continue; } if (current.type === 'adhoc') { - copy.templating.list[i].filters = original.filters; + current.filters = original.filters; } else { - copy.templating.list[i].current = original.current; + current.current = original.current; } } } @@ -384,18 +350,14 @@ export class DashboardModel implements TimeModel { this.events.publish(new RefreshEvent()); this.lastRefresh = Date.now(); - if (this.panelInEdit) { - if (event.refreshAll || event.panelIds.includes(this.panelInEdit.id)) { - this.panelInEdit.refresh(); - return; - } + if (this.panelInEdit && (event.refreshAll || event.panelIds.includes(this.panelInEdit.id))) { + this.panelInEdit.refresh(); + return; } for (const panel of this.panels) { - if (!this.otherPanelInFullscreen(panel)) { - if (event.refreshAll || event.panelIds.includes(panel.id)) { - panel.refresh(); - } + if (!this.otherPanelInFullscreen(panel) && (event.refreshAll || event.panelIds.includes(panel.id))) { + panel.refresh(); } } } @@ -453,51 +415,40 @@ export class DashboardModel implements TimeModel { } private ensurePanelsHaveIds() { - for (const panel of this.panels) { - if (!panel.id) { - panel.id = this.getNextPanelId(); - } - - if (panel.panels) { - for (const rowPanel of panel.panels) { - if (!rowPanel.id) { - rowPanel.id = this.getNextPanelId(); - } - } - } + let nextPanelId = this.getNextPanelId(); + for (const panel of this.panelIterator()) { + panel.id ??= nextPanelId++; } } - private ensureListExist(data: any) { - if (!data) { - data = {}; - } - if (!data.list) { - data.list = []; - } + private ensureListExist(data: any = {}) { + data.list ??= []; return data; } getNextPanelId() { let max = 0; - for (const panel of this.panels) { + for (const panel of this.panelIterator()) { if (panel.id > max) { max = panel.id; } - - if (panel.collapsed) { - for (const rowPanel of panel.panels) { - if (rowPanel.id > max) { - max = rowPanel.id; - } - } - } } return max + 1; } + *panelIterator() { + for (const panel of this.panels) { + yield panel; + + const rowPanels = panel.panels ?? []; + for (const rowPanel of rowPanels) { + yield rowPanel as PanelModel; + } + } + } + forEachPanel(callback: (panel: PanelModel, index: number) => void) { for (let i = 0; i < this.panels.length; i++) { callback(this.panels[i], i); @@ -509,13 +460,7 @@ export class DashboardModel implements TimeModel { return this.panelInEdit; } - for (const panel of this.panels) { - if (panel.id === id) { - return panel; - } - } - - return null; + return this.panels.find((p) => p.id === id) ?? null; } canEditPanel(panel?: PanelModel | null): boolean | undefined | null { @@ -553,13 +498,12 @@ export class DashboardModel implements TimeModel { } hasUnsavedChanges() { - for (const panel of this.panels) { - if (panel.hasChanged) { - console.log('Panel has changed', panel); - return true; - } + const changedPanel = this.panels.find((p) => p.hasChanged); + if (changedPanel) { + console.log('Panel has changed', changedPanel); } - return false; + + return Boolean(changedPanel); } cleanUpRepeats() { @@ -568,17 +512,12 @@ export class DashboardModel implements TimeModel { } this.iteration = (this.iteration || new Date().getTime()) + 1; - const panelsToRemove = []; - // cleanup scopedVars deleteScopeVars(this.panels); - for (let i = 0; i < this.panels.length; i++) { - const panel = this.panels[i]; - if ((!panel.repeat || panel.repeatedByRow) && panel.repeatPanelId && panel.repeatIteration !== this.iteration) { - panelsToRemove.push(panel); - } - } + const panelsToRemove = this.panels.filter( + (p) => (!p.repeat || p.repeatedByRow) && p.repeatPanelId && p.repeatIteration !== this.iteration + ); // remove panels pull(this.panels, ...panelsToRemove); @@ -607,13 +546,8 @@ export class DashboardModel implements TimeModel { } cleanUpRowRepeats(rowPanels: PanelModel[]) { - const panelsToRemove = []; - for (let i = 0; i < rowPanels.length; i++) { - const panel = rowPanels[i]; - if (!panel.repeat && panel.repeatPanelId) { - panelsToRemove.push(panel); - } - } + const panelsToRemove = rowPanels.filter((p) => !p.repeat && p.repeatPanelId); + pull(rowPanels, ...panelsToRemove); pull(this.panels, ...panelsToRemove); } @@ -623,18 +557,17 @@ export class DashboardModel implements TimeModel { return; } - let rowPanels = row.panels; + let rowPanels = row.panels ?? []; if (!row.collapsed) { - const rowPanelIndex = findIndex(this.panels, (p: PanelModel) => p.id === row.id); + const rowPanelIndex = this.panels.findIndex((p) => p.id === row.id); rowPanels = this.getRowPanels(rowPanelIndex); } this.cleanUpRowRepeats(rowPanels); - for (let i = 0; i < rowPanels.length; i++) { - const panel = rowPanels[i]; + for (const panel of rowPanels) { if (panel.repeat) { - const panelIndex = findIndex(this.panels, (p: PanelModel) => p.id === panel.id); + const panelIndex = this.panels.findIndex((p) => p.id === panel.id); this.repeatPanel(panel, panelIndex); } } @@ -679,13 +612,13 @@ export class DashboardModel implements TimeModel { // for row clones we need to figure out panels under row to clone and where to insert clone let rowPanels: PanelModel[], insertPos: number; if (sourceRowPanel.collapsed) { - rowPanels = cloneDeep(sourceRowPanel.panels); + rowPanels = cloneDeep(sourceRowPanel.panels) ?? []; clone.panels = rowPanels; // insert copied row after preceding row insertPos = sourcePanelIndex + valueIndex; } else { rowPanels = this.getRowPanels(sourcePanelIndex); - clone.panels = map(rowPanels, (panel: PanelModel) => panel.getSaveModel()); + clone.panels = rowPanels.map((panel) => panel.getSaveModel()); // insert copied row after preceding row's panels insertPos = sourcePanelIndex + (rowPanels.length + 1) * valueIndex; } @@ -696,7 +629,7 @@ export class DashboardModel implements TimeModel { } repeatPanel(panel: PanelModel, panelIndex: number) { - const variable: any = this.getPanelRepeatVariable(panel); + const variable = this.getPanelRepeatVariable(panel); if (!variable) { return; } @@ -717,7 +650,7 @@ export class DashboardModel implements TimeModel { let copy; copy = this.getPanelRepeatClone(panel, index, panelIndex); - copy.scopedVars = copy.scopedVars || {}; + copy.scopedVars ??= {}; copy.scopedVars[variable.name] = option; if (panel.repeatDirection === REPEAT_DIR_VERTICAL) { @@ -746,12 +679,12 @@ export class DashboardModel implements TimeModel { const yOffset = yPos - panel.gridPos.y; if (yOffset > 0) { const panelBelowIndex = panelIndex + selectedOptions.length; - for (let i = panelBelowIndex; i < this.panels.length; i++) { - if (isOnTheSameGridRow(panel, this.panels[i])) { + for (const curPanel of this.panels.slice(panelBelowIndex)) { + if (isOnTheSameGridRow(panel, curPanel)) { continue; } - this.panels[i].gridPos.y += yOffset; + curPanel.gridPos.y += yOffset; } } } @@ -761,7 +694,7 @@ export class DashboardModel implements TimeModel { let yPos = panel.gridPos.y; function setScopedVars(panel: PanelModel, variableOption: any) { - panel.scopedVars = panel.scopedVars || {}; + panel.scopedVars ??= {}; panel.scopedVars[variable.name] = variableOption; } @@ -776,19 +709,19 @@ export class DashboardModel implements TimeModel { if (panel.collapsed) { // For collapsed row just copy its panels and set scoped vars and proper IDs - each(rowPanels, (rowPanel: PanelModel, i: number) => { + for (const rowPanel of rowPanels) { setScopedVars(rowPanel, option); if (optionIndex > 0) { this.updateRepeatedPanelIds(rowPanel, true); } - }); + } rowCopy.gridPos.y += optionIndex; yPos += optionIndex; panelBelowIndex = panelIndex + optionIndex + 1; } else { // insert after 'row' panel const insertPos = panelIndex + (rowPanels.length + 1) * optionIndex + 1; - each(rowPanels, (rowPanel: PanelModel, i: number) => { + rowPanels.forEach((rowPanel: PanelModel, i: number) => { setScopedVars(rowPanel, option); if (optionIndex > 0) { const cloneRowPanel = new PanelModel(rowPanel); @@ -806,8 +739,8 @@ export class DashboardModel implements TimeModel { // Update gridPos for panels below if we inserted more than 1 repeated row panel if (selectedOptions.length > 1) { - for (let i = panelBelowIndex; i < this.panels.length; i++) { - this.panels[i].gridPos.y += yPos; + for (const panel of this.panels.slice(panelBelowIndex)) { + panel.gridPos.y += yPos; } } } @@ -839,11 +772,10 @@ export class DashboardModel implements TimeModel { if (!rowPanel.panels || rowPanel.panels.length === 0) { return 0; } + const rowYPos = rowPanel.gridPos.y; const positions = map(rowPanel.panels, 'gridPos'); - const maxPos = maxBy(positions, (pos: GridPos) => { - return pos.y + pos.h; - }); + const maxPos = maxBy(positions, (pos: GridPos) => pos.y + pos.h); return maxPos!.y + maxPos!.h - rowYPos; } @@ -853,9 +785,9 @@ export class DashboardModel implements TimeModel { } removeRow(row: PanelModel, removePanels: boolean) { - const needToogle = (!removePanels && row.collapsed) || (removePanels && !row.collapsed); + const needToggle = (!removePanels && row.collapsed) || (removePanels && !row.collapsed); - if (needToogle) { + if (needToggle) { this.toggleRow(row); } @@ -863,60 +795,30 @@ export class DashboardModel implements TimeModel { } expandRows() { - for (let i = 0; i < this.panels.length; i++) { - const panel = this.panels[i]; - - if (panel.type !== 'row') { - continue; - } - - if (panel.collapsed) { - this.toggleRow(panel); - } + const collapsedRows = this.panels.filter((p) => p.type === 'row' && p.collapsed); + for (const row of collapsedRows) { + this.toggleRow(row); } } collapseRows() { - for (let i = 0; i < this.panels.length; i++) { - const panel = this.panels[i]; - - if (panel.type !== 'row') { - continue; - } - - if (!panel.collapsed) { - this.toggleRow(panel); - } + const collapsedRows = this.panels.filter((p) => p.type === 'row' && !p.collapsed); + for (const row of collapsedRows) { + this.toggleRow(row); } } isSubMenuVisible() { - if (this.links.length > 0) { - return true; - } - - if (this.getVariables().find((variable) => variable.hide !== 2)) { - return true; - } - - if (this.annotations.list.find((annotation) => annotation.hide !== true)) { - return true; - } - - return false; + return ( + this.links.length > 0 || + this.getVariables().some((variable) => variable.hide !== 2) || + this.annotations.list.some((annotation) => !annotation.hide) + ); } getPanelInfoById(panelId: number) { - for (let i = 0; i < this.panels.length; i++) { - if (this.panels[i].id === panelId) { - return { - panel: this.panels[i], - index: i, - }; - } - } - - return null; + const panelIndex = this.panels.findIndex((p) => p.id === panelId); + return panelIndex >= 0 ? { panel: this.panels[panelIndex], index: panelIndex } : null; } duplicatePanel(panel: PanelModel) { @@ -962,64 +864,64 @@ export class DashboardModel implements TimeModel { toggleRow(row: PanelModel) { const rowIndex = indexOf(this.panels, row); - if (row.collapsed) { - row.collapsed = false; - const hasRepeat = some(row.panels as PanelModel[], (p: PanelModel) => p.repeat); + if (!row.collapsed) { + const rowPanels = this.getRowPanels(rowIndex); - if (row.panels.length > 0) { - // Use first panel to figure out if it was moved or pushed - // If the panel doesn't have gridPos.y, use the row gridPos.y instead. - // This can happen for some generated dashboards. - const firstPanelYPos = row.panels[0].gridPos.y ?? row.gridPos.y; - const yDiff = firstPanelYPos - (row.gridPos.y + row.gridPos.h); - - // start inserting after row - let insertPos = rowIndex + 1; - // y max will represent the bottom y pos after all panels have been added - // needed to know home much panels below should be pushed down - let yMax = row.gridPos.y; - - for (const panel of row.panels) { - // set the y gridPos if it wasn't already set - panel.gridPos.y ?? (panel.gridPos.y = row.gridPos.y); // (Safari 13.1 lacks ??= support) - // make sure y is adjusted (in case row moved while collapsed) - panel.gridPos.y -= yDiff; - // insert after row - this.panels.splice(insertPos, 0, new PanelModel(panel)); - // update insert post and y max - insertPos += 1; - yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h); - } - - const pushDownAmount = yMax - row.gridPos.y - 1; - - // push panels below down - for (let panelIndex = insertPos; panelIndex < this.panels.length; panelIndex++) { - this.panels[panelIndex].gridPos.y += pushDownAmount; - } - - row.panels = []; - - if (hasRepeat) { - this.processRowRepeats(row); - } - } - - // sort panels - this.sortPanelsByGridPos(); + // remove panels + pull(this.panels, ...rowPanels); + // save panel models inside row panel + row.panels = rowPanels.map((panel: PanelModel) => panel.getSaveModel()); + row.collapsed = true; // emit change event this.events.publish(new DashboardPanelsChangedEvent()); return; } - const rowPanels = this.getRowPanels(rowIndex); + row.collapsed = false; + const rowPanels = row.panels ?? []; + const hasRepeat = rowPanels.some((p: PanelModel) => p.repeat); + if (rowPanels.length > 0) { + // Use first panel to figure out if it was moved or pushed + // If the panel doesn't have gridPos.y, use the row gridPos.y instead. + // This can happen for some generated dashboards. + const firstPanelYPos = rowPanels[0].gridPos.y ?? row.gridPos.y; + const yDiff = firstPanelYPos - (row.gridPos.y + row.gridPos.h); - // remove panels - pull(this.panels, ...rowPanels); - // save panel models inside row panel - row.panels = map(rowPanels, (panel: PanelModel) => panel.getSaveModel()); - row.collapsed = true; + // start inserting after row + let insertPos = rowIndex + 1; + // y max will represent the bottom y pos after all panels have been added + // needed to know home much panels below should be pushed down + let yMax = row.gridPos.y; + + for (const panel of rowPanels) { + // set the y gridPos if it wasn't already set + panel.gridPos.y ?? (panel.gridPos.y = row.gridPos.y); // (Safari 13.1 lacks ??= support) + // make sure y is adjusted (in case row moved while collapsed) + panel.gridPos.y -= yDiff; + // insert after row + this.panels.splice(insertPos, 0, new PanelModel(panel)); + // update insert post and y max + insertPos += 1; + yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h); + } + + const pushDownAmount = yMax - row.gridPos.y - 1; + + // push panels below down + for (const panel of this.panels.slice(insertPos)) { + panel.gridPos.y += pushDownAmount; + } + + row.panels = []; + + if (hasRepeat) { + this.processRowRepeats(row); + } + } + + // sort panels + this.sortPanelsByGridPos(); // emit change event this.events.publish(new DashboardPanelsChangedEvent()); @@ -1029,19 +931,11 @@ export class DashboardModel implements TimeModel { * Will return all panels after rowIndex until it encounters another row */ getRowPanels(rowIndex: number): PanelModel[] { - const rowPanels = []; + const panelsBelowRow = this.panels.slice(rowIndex + 1); + const nextRowIndex = panelsBelowRow.findIndex((p) => p.type === 'row'); - for (let index = rowIndex + 1; index < this.panels.length; index++) { - const panel = this.panels[index]; - - // break when encountering another row - if (panel.type === 'row') { - break; - } - - // this panel must belong to row - rowPanels.push(panel); - } + // Take all panels up to next row, or all panels if there are no other rows + const rowPanels = panelsBelowRow.slice(0, nextRowIndex >= 0 ? nextRowIndex : this.panels.length); return rowPanels; } @@ -1095,14 +989,12 @@ export class DashboardModel implements TimeModel { hasTimeChanged() { const { time, originalTime } = this; - if (isEqual(time, originalTime)) { - return false; - } - // Compare momemt values vs strings values + // Compare moment values vs strings values return !( - isEqual(dateTime(time?.from), dateTime(originalTime?.from)) && - isEqual(dateTime(time?.to), dateTime(originalTime?.to)) + isEqual(time, originalTime) || + (isEqual(dateTime(time?.from), dateTime(originalTime?.from)) && + isEqual(dateTime(time?.to), dateTime(originalTime?.to))) ); } @@ -1120,11 +1012,7 @@ export class DashboardModel implements TimeModel { } autoFitPanels(viewHeight: number, kioskMode?: UrlQueryValue) { - const currentGridHeight = Math.max( - ...this.panels.map((panel) => { - return panel.gridPos.h + panel.gridPos.y; - }) - ); + const currentGridHeight = Math.max(...this.panels.map((panel) => panel.gridPos.h + panel.gridPos.y)); const navbarHeight = 55; const margin = 20; @@ -1145,10 +1033,10 @@ export class DashboardModel implements TimeModel { const visibleGridHeight = Math.floor(visibleHeight / (GRID_CELL_HEIGHT + GRID_CELL_VMARGIN)); const scaleFactor = currentGridHeight / visibleGridHeight; - this.panels.forEach((panel, i) => { + for (const panel of this.panels) { panel.gridPos.y = Math.round(panel.gridPos.y / scaleFactor) || 1; panel.gridPos.h = Math.round(panel.gridPos.h / scaleFactor) || 1; - }); + } } templateVariableValueUpdated() { @@ -1160,39 +1048,32 @@ export class DashboardModel implements TimeModel { const panelId = parseInt(panelUrlId ?? '0', 10); // First try to find it in a collapsed row and exand it - for (const panel of this.panels) { - if (panel.collapsed) { - for (const rowPanel of panel.panels) { - if (rowPanel.id === panelId) { - this.toggleRow(panel); - break; - } - } - } + const collapsedPanels = this.panels.filter((p) => p.collapsed); + for (const panel of collapsedPanels) { + const hasPanel = panel.panels?.some((rp: any) => rp.id === panelId); + hasPanel && this.toggleRow(panel); } return this.getPanelById(panelId); } toggleLegendsForAll() { - const panelsWithLegends = this.panels.filter((panel) => { - return panel.legend !== undefined && panel.legend !== null; - }); + const panelsWithLegends = this.panels.filter(isPanelWithLegend); // determine if more panels are displaying legends or not - const onCount = panelsWithLegends.filter((panel) => panel.legend!.show).length; + const onCount = panelsWithLegends.filter((panel) => panel.legend.show).length; const offCount = panelsWithLegends.length - onCount; const panelLegendsOn = onCount >= offCount; for (const panel of panelsWithLegends) { - panel.legend!.show = !panelLegendsOn; + panel.legend.show = !panelLegendsOn; panel.render(); } } - getVariables = () => { + getVariables() { return this.getVariablesFromState(this.uid); - }; + } canEditAnnotations(dashboardId: number) { let canEdit = true; @@ -1209,13 +1090,8 @@ export class DashboardModel implements TimeModel { } canAddAnnotations() { - let canAdd = true; - - // if RBAC is enabled there are additional conditions to check - if (contextSrv.accessControlEnabled()) { - canAdd = !!this.meta.annotationsPermissions?.dashboard.canAdd; - } - + // If RBAC is enabled there are additional conditions to check + const canAdd = !contextSrv.accessControlEnabled() || this.meta.annotationsPermissions?.dashboard.canAdd; return this.canEditDashboard() && canAdd; } @@ -1247,27 +1123,23 @@ export class DashboardModel implements TimeModel { return false; } - const updated = map(currentVariables, (variable: any) => { - return { - name: variable.name, - type: variable.type, - current: cloneDeep(variable.current), - filters: cloneDeep(variable.filters), - }; - }); + const updated = currentVariables.map((variable: any) => ({ + name: variable.name, + type: variable.type, + current: cloneDeep(variable.current), + filters: cloneDeep(variable.filters), + })); return !isEqual(updated, originalVariables); } private cloneVariablesFrom(variables: any[]): any[] { - return variables.map((variable) => { - return { - name: variable.name, - type: variable.type, - current: cloneDeep(variable.current), - filters: cloneDeep(variable.filters), - }; - }); + return variables.map((variable) => ({ + name: variable.name, + type: variable.type, + current: cloneDeep(variable.current), + filters: cloneDeep(variable.filters), + })); } private variablesTimeRangeProcessDoneHandler(event: VariablesTimeRangeProcessDone) { @@ -1299,3 +1171,7 @@ export class DashboardModel implements TimeModel { this.startRefresh(event.payload); } } + +function isPanelWithLegend(panel: PanelModel): panel is PanelModel & Pick, 'legend'> { + return Boolean(panel.legend); +} diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 81723a24d62..b4a64914493 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -139,7 +139,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { maxPerRow?: number; collapsed?: boolean; - panels?: any; + panels?: PanelModel[]; declare targets: DataQuery[]; transformations?: DataTransformerConfig[]; datasource: DataSourceRef | null = null; diff --git a/public/app/features/dashboard/state/utils.test.ts b/public/app/features/dashboard/state/utils.test.ts index 5c63afdf2ee..1e0f10bb20f 100644 --- a/public/app/features/dashboard/state/utils.test.ts +++ b/public/app/features/dashboard/state/utils.test.ts @@ -50,14 +50,14 @@ describe('deleteScopeVars', () => { }); expect(panel1.scopedVars).toBeDefined(); - expect(panel1.panels[0].scopedVars).toBeDefined(); - expect(panel1.panels[1].scopedVars).toBeDefined(); + expect(panel1.panels?.[0].scopedVars).toBeDefined(); + expect(panel1.panels?.[1].scopedVars).toBeDefined(); deleteScopeVars([panel1]); expect(panel1.scopedVars).toBeUndefined(); - expect(panel1.panels[0].scopedVars).toBeUndefined(); - expect(panel1.panels[1].scopedVars).toBeUndefined(); + expect(panel1.panels?.[0].scopedVars).toBeUndefined(); + expect(panel1.panels?.[1].scopedVars).toBeUndefined(); }); }); });