mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
Closes https://github.com/elastic/kibana/issues/205531 Closes #219877. Closes https://github.com/elastic/kibana/issues/213153 Closes https://github.com/elastic/kibana/issues/150920 Closes https://github.com/elastic/kibana/issues/203130 ### Overview The embeddable framework has two types of state: `SerializedState` and `RuntimeState`. `SerializedState` is the form of the state when saved into a Dashboard saved object. I.e. the References are extracted, and state saved externally (by reference) is removed. In contrast `RuntimeState` is an exact snapshot of the state used by the embeddable to render. <b>Exposing SerializedState and RuntimeState was a mistake</b> that caused numerous regressions and architectural complexities. This PR simplifies the embeddable framework by only exposing `SerializedState`. `RuntimeState` stays localized to the embeddable implementation and is never leaked to the embeddable framework. ### Whats changed * `ReactEmbeddableFactory<SerializedState, RuntimeState, Api>` => `EmbeddableFactory<SerializedState, Api>` * `deserializeState` removed from embeddable factory. Instead, `SerializedState` is passed directly into `buildEmbeddable`. * `buildEmbeddable` parameter `buildApi` replaced with `finalizeApi`. `buildApi({ api, comparators })` => `finalizeApi(api)`. * The embeddable framework previously used its knowledge of `RuntimeState` to setup and monitor unsaved changes. Now, unsaved changes setup is pushed down to the embeddable implementation since the embeddable framework no longer has knowledge of embeddable RuntimeState. ### Reviewer instructions <b>Please prioritize reviews.</b> This is a large effort from our team and is blocking many other initiatives. Getting this merged is a top priority. This is a large change that would best be reviewed by manually testing the changes * adding/editing your embeddable types * Ensuring dashboard shows unsaved changes as expected * Ensuring dashboard resets unsaved changes as expected * Ensuring dashboard does not show unsaved changes after save and reset * Returning to a dashboard with unsaved changes renders embeddables with those unsaved changes --------- Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com> Co-authored-by: Nathan Reese <reese.nathan@elastic.co> Co-authored-by: Nick Peihl <nick.peihl@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Catherine Liu <catherine.liu@elastic.co> Co-authored-by: Ola Pawlus <98127445+olapawlus@users.noreply.github.com>
126 lines
4.4 KiB
TypeScript
126 lines
4.4 KiB
TypeScript
/*
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
* or more contributor license agreements. Licensed under the "Elastic License
|
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
* Public License v 1"; you may not use this file except in compliance with, at
|
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
*/
|
|
|
|
import { cloneDeep } from 'lodash';
|
|
import { useMemo } from 'react';
|
|
import { BehaviorSubject } from 'rxjs';
|
|
import { v4 } from 'uuid';
|
|
|
|
import { TimeRange } from '@kbn/es-query';
|
|
import { PanelPackage } from '@kbn/presentation-containers';
|
|
|
|
import { ViewMode } from '@kbn/presentation-publishing';
|
|
import {
|
|
MockDashboardApi,
|
|
MockSerializedDashboardState,
|
|
MockedDashboardPanelMap,
|
|
MockedDashboardRowMap,
|
|
} from './types';
|
|
|
|
const DASHBOARD_GRID_COLUMN_COUNT = 48;
|
|
const DEFAULT_PANEL_HEIGHT = 15;
|
|
const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2;
|
|
|
|
export const useMockDashboardApi = ({
|
|
savedState,
|
|
}: {
|
|
savedState: MockSerializedDashboardState;
|
|
}): MockDashboardApi => {
|
|
const mockDashboardApi = useMemo(() => {
|
|
const panels$ = new BehaviorSubject<MockedDashboardPanelMap>(savedState.panels);
|
|
const expandedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
|
const viewMode$ = new BehaviorSubject<ViewMode>('edit');
|
|
|
|
return {
|
|
getSerializedStateForChild: (id: string) => {
|
|
return {
|
|
rawState: panels$.getValue()[id].explicitInput,
|
|
references: [],
|
|
};
|
|
},
|
|
children$: new BehaviorSubject({}),
|
|
timeRange$: new BehaviorSubject<TimeRange>({
|
|
from: 'now-24h',
|
|
to: 'now',
|
|
}),
|
|
filters$: new BehaviorSubject([]),
|
|
query$: new BehaviorSubject(''),
|
|
viewMode$,
|
|
setViewMode: (viewMode: ViewMode) => viewMode$.next(viewMode),
|
|
panels$,
|
|
getPanelCount: () => {
|
|
return Object.keys(panels$.getValue()).length;
|
|
},
|
|
rows$: new BehaviorSubject<MockedDashboardRowMap>(savedState.rows),
|
|
expandedPanelId$,
|
|
expandPanel: (id: string) => {
|
|
if (expandedPanelId$.getValue()) {
|
|
expandedPanelId$.next(undefined);
|
|
} else {
|
|
expandedPanelId$.next(id);
|
|
}
|
|
},
|
|
removePanel: (id: string) => {
|
|
const panels = { ...mockDashboardApi.panels$.getValue() };
|
|
delete panels[id]; // the grid layout component will handle compacting, if necessary
|
|
mockDashboardApi.panels$.next(panels);
|
|
},
|
|
replacePanel: async (id: string, newPanel: PanelPackage): Promise<string> => {
|
|
const currentPanels = mockDashboardApi.panels$.getValue();
|
|
const otherPanels = { ...currentPanels };
|
|
const oldPanel = currentPanels[id];
|
|
delete otherPanels[id];
|
|
const newId = v4();
|
|
otherPanels[newId] = {
|
|
...oldPanel,
|
|
explicitInput: { ...(newPanel.serializedState?.rawState ?? {}), id: newId },
|
|
};
|
|
mockDashboardApi.panels$.next(otherPanels);
|
|
return newId;
|
|
},
|
|
addNewPanel: async (panelPackage: PanelPackage): Promise<undefined> => {
|
|
// we are only implementing "place at top" here, for demo purposes
|
|
const currentPanels = mockDashboardApi.panels$.getValue();
|
|
const otherPanels = { ...currentPanels };
|
|
for (const [id, panel] of Object.entries(currentPanels)) {
|
|
const currentPanel = cloneDeep(panel);
|
|
currentPanel.gridData.y = currentPanel.gridData.y + DEFAULT_PANEL_HEIGHT;
|
|
otherPanels[id] = currentPanel;
|
|
}
|
|
const newId = v4();
|
|
mockDashboardApi.panels$.next({
|
|
...otherPanels,
|
|
[newId]: {
|
|
type: panelPackage.panelType,
|
|
gridData: {
|
|
row: 'first',
|
|
x: 0,
|
|
y: 0,
|
|
w: DEFAULT_PANEL_WIDTH,
|
|
h: DEFAULT_PANEL_HEIGHT,
|
|
i: newId,
|
|
},
|
|
explicitInput: {
|
|
...(panelPackage.serializedState?.rawState ?? {}),
|
|
id: newId,
|
|
},
|
|
},
|
|
});
|
|
},
|
|
canRemovePanels: () => true,
|
|
getChildApi: () => {
|
|
throw new Error('getChildApi implemenation not provided');
|
|
},
|
|
};
|
|
// only run onMount
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
return mockDashboardApi;
|
|
};
|