mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import { PanelModel as IPanelModel } from '@grafana/data';
|
|
import { isEqualWith } from 'lodash';
|
|
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;
|
|
}
|