kibana/examples/embeddable_examples/public/app/render_examples.tsx
Devon Thomson 3e882d8cd9
[Embeddables] Serialized State Only (#215947)
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>
2025-05-06 15:08:34 -06:00

131 lines
3.9 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 React, { useMemo, useState } from 'react';
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import {
EuiCodeBlock,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiSuperDatePicker,
EuiSwitch,
EuiText,
OnTimeChangeProps,
} from '@elastic/eui';
import { BehaviorSubject, Subject } from 'rxjs';
import { TimeRange } from '@kbn/es-query';
import { useBatchedOptionalPublishingSubjects } from '@kbn/presentation-publishing';
import { SearchEmbeddableRenderer } from '../react_embeddables/search/search_embeddable_renderer';
import { SEARCH_EMBEDDABLE_TYPE } from '../react_embeddables/search/constants';
import type { SearchApi, SearchSerializedState } from '../react_embeddables/search/types';
export const RenderExamples = () => {
const parentApi = useMemo(() => {
return {
reload$: new Subject<void>(),
getSerializedStateForChild: () => ({
rawState: {
timeRange: undefined,
},
}),
timeRange$: new BehaviorSubject<TimeRange>({
from: 'now-24h',
to: 'now',
}),
};
// only run onMount
}, []);
const [api, setApi] = useState<SearchApi | null>(null);
const [hidePanelChrome, setHidePanelChrome] = useState<boolean>(false);
const [dataLoading, timeRange] = useBatchedOptionalPublishingSubjects(
api?.dataLoading$,
parentApi.timeRange$
);
return (
<div>
<EuiSuperDatePicker
isLoading={dataLoading ? dataLoading : false}
start={timeRange.from}
end={timeRange.to}
onTimeChange={({ start, end }: OnTimeChangeProps) => {
parentApi.timeRange$.next({
from: start,
to: end,
});
}}
onRefresh={() => {
parentApi.reload$.next();
}}
/>
<EuiSpacer size="s" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiText>
<p>
Use <strong>EmbeddableRenderer</strong> to render embeddables.
</p>
</EuiText>
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{`<EmbeddableRenderer<State, Api>
type={SEARCH_EMBEDDABLE_TYPE}
getParentApi={() => parentApi}
onApiAvailable={(newApi) => {
setApi(newApi);
}}
hidePanelChrome={hidePanelChrome}
/>`}
</EuiCodeBlock>
<EuiSpacer size="s" />
<EuiSwitch
label="Set hidePanelChrome to render embeddable without Panel wrapper."
checked={hidePanelChrome}
onChange={(e) => setHidePanelChrome(e.target.checked)}
/>
<EuiSpacer size="s" />
<EmbeddableRenderer<SearchSerializedState, SearchApi>
key={hidePanelChrome ? 'hideChrome' : 'showChrome'}
type={SEARCH_EMBEDDABLE_TYPE}
getParentApi={() => parentApi}
onApiAvailable={(newApi) => {
setApi(newApi);
}}
hidePanelChrome={hidePanelChrome}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<p>To avoid leaking embeddable details, wrap EmbeddableRenderer in a component.</p>
</EuiText>
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
{`<SearchEmbeddableRenderer
timeRange={timeRange}
/>`}
</EuiCodeBlock>
<EuiSpacer size="s" />
<SearchEmbeddableRenderer timeRange={timeRange} />
</EuiFlexItem>
</EuiFlexGroup>
</div>
);
};