grafana/public/app/features/dashboard/utils/panelMerge.ts
Josh Hunt 3c6e0e8ef8
Chore: ESlint import order (#44959)
* Add and configure eslint-plugin-import

* Fix the lint:ts npm command

* Autofix + prettier all the files

* Manually fix remaining files

* Move jquery code in jest-setup to external file to safely reorder imports

* Resolve issue caused by circular dependencies within Prometheus

* Update .betterer.results

* Fix missing // @ts-ignore

* ignore iconBundle.ts

* Fix missing // @ts-ignore
2022-04-22 14:33:13 +01:00

134 lines
3.3 KiB
TypeScript

import { isEqualWith } from 'lodash';
import { PanelModel as IPanelModel } from '@grafana/data';
import { PanelModel } from '../state';
export interface PanelMergeInfo {
changed: boolean;
panels: PanelModel[];
actions: Record<string, number[]>;
}
// Values that are safe to change without a full panel unmount/remount
// TODO: options and fieldConfig should also be supported
const mutableKeys = new Set<keyof PanelModel>(['gridPos', 'title', 'description', 'transparent']);
export function mergePanels(current: PanelModel[], data: IPanelModel[]): PanelMergeInfo {
const panels: PanelModel[] = [];
const info = {
changed: false,
actions: {
add: [] as number[],
remove: [] as number[],
replace: [] as number[],
update: [] as number[],
noop: [] as number[],
},
panels,
};
let nextId = 0;
const inputPanels = new Map<number, IPanelModel>();
for (let p of data) {
let { id } = p;
if (!id) {
if (!nextId) {
nextId = findNextPanelID([current, data]);
}
id = nextId++;
p = { ...p, id }; // clone with new ID
}
inputPanels.set(id, p);
}
for (const panel of current) {
const target = inputPanels.get(panel.id) as PanelModel;
if (!target) {
info.changed = true;
info.actions.remove.push(panel.id);
panel.destroy();
continue;
}
inputPanels.delete(panel.id);
// Fast comparison when working with the same panel objects
if (target === panel) {
panels.push(panel);
info.actions.noop.push(panel.id);
continue;
}
// Check if it is the same type
if (panel.type === target.type) {
const save = panel.getSaveModel();
let isNoop = true;
let doUpdate = false;
for (const [key, value] of Object.entries(target)) {
if (!isEqualWith(value, save[key], infinityEqualsNull)) {
info.changed = true;
isNoop = false;
if (mutableKeys.has(key as any)) {
(panel as any)[key] = value;
doUpdate = true;
} else {
doUpdate = false;
break; // needs full replace
}
}
}
if (isNoop) {
panels.push(panel);
info.actions.noop.push(panel.id);
continue;
}
if (doUpdate) {
panels.push(panel);
info.actions.update.push(panel.id);
continue;
}
}
panel.destroy();
const next = new PanelModel(target);
next.key = `${next.id}-update-${Date.now()}`; // force react invalidate
panels.push(next);
info.changed = true;
info.actions.replace.push(panel.id);
}
// Add the new panels
for (const t of inputPanels.values()) {
panels.push(new PanelModel(t));
info.changed = true;
info.actions.add.push(t.id);
}
return info;
}
// Since +- Infinity are saved as null in JSON, we need to make them equal here also
function infinityEqualsNull(a: any, b: any) {
if (a == null && (b === Infinity || b === -Infinity || b == null)) {
return true;
}
if (b == null && (a === Infinity || a === -Infinity || a == null)) {
return true;
}
return undefined; // use default comparison
}
function findNextPanelID(args: IPanelModel[][]): number {
let max = 0;
for (const panels of args) {
for (const panel of panels) {
if (panel.id > max) {
max = panel.id;
}
}
}
return max + 1;
}