123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- 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;
- }
|