Skip to content

Commit

Permalink
[Embeddables Rebuild] Make Serialize Function Synchronous (#203662)
Browse files Browse the repository at this point in the history
changes the signature of the `serializeState` function so that
it no longer returns MaybePromise
  • Loading branch information
ThomThomson authored Dec 13, 2024
1 parent 02a2ff1 commit abfd590
Show file tree
Hide file tree
Showing 21 changed files with 156 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,10 @@ export const ReactControlExample = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
onClick={async () => {
onClick={() => {
if (controlGroupApi) {
saveNotification$.next();
setControlGroupSerializedState(await controlGroupApi.serializeState());
setControlGroupSerializedState(controlGroupApi.serializeState());
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import { BehaviorSubject, Subject, combineLatest, map, merge } from 'rxjs';
import { v4 as generateId } from 'uuid';
import { asyncForEach } from '@kbn/std';
import { TimeRange } from '@kbn/es-query';
import {
PanelPackage,
Expand Down Expand Up @@ -146,14 +145,14 @@ export function getPageApi() {
},
onSave: async () => {
const panelsState: LastSavedState['panelsState'] = [];
await asyncForEach(panels$.value, async ({ id, type }) => {
panels$.value.forEach(({ id, type }) => {
try {
const childApi = children$.value[id];
if (apiHasSerializableState(childApi)) {
panelsState.push({
id,
type,
panelState: await childApi.serializeState(),
panelState: childApi.serializeState(),
});
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/presentation-publishing';
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import { BehaviorSubject, Subject } from 'rxjs';
import useMountedState from 'react-use/lib/useMountedState';
import { SAVED_BOOK_ID } from '../../react_embeddables/saved_book/constants';
import {
BookApi,
Expand All @@ -32,7 +31,6 @@ import { lastSavedStateSessionStorage } from './last_saved_state';
import { unsavedChangesSessionStorage } from './unsaved_changes';

export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStart }) => {
const isMounted = useMountedState();
const saveNotification$ = useMemo(() => {
return new Subject<void>();
}, []);
Expand Down Expand Up @@ -123,16 +121,13 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar
<EuiFlexItem grow={false}>
<EuiButton
disabled={isSaving || !hasUnsavedChanges}
onClick={async () => {
onClick={() => {
if (!bookApi) {
return;
}

setIsSaving(true);
const bookSerializedState = await bookApi.serializeState();
if (!isMounted()) {
return;
}
const bookSerializedState = bookApi.serializeState();
lastSavedStateSessionStorage.save(bookSerializedState);
saveNotification$.next(); // signals embeddable unsaved change tracking to update last saved state
setHasUnsavedChanges(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { ADD_PANEL_TRIGGER, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import {
Expand All @@ -21,7 +21,6 @@ import {
} from './book_state';
import { ADD_SAVED_BOOK_ACTION_ID, SAVED_BOOK_ID } from './constants';
import { openSavedBookEditor } from './saved_book_editor';
import { saveBookAttributes } from './saved_book_library';
import { BookRuntimeState } from './types';

export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, core: CoreStart) => {
Expand All @@ -36,19 +35,17 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
if (!apiCanAddNewPanel(embeddable)) throw new IncompatibleActionError();
const newPanelStateManager = stateManagerFromAttributes(defaultBookAttributes);

const { addToLibrary } = await openSavedBookEditor(newPanelStateManager, true, core, {
parentApi: embeddable,
const { savedBookId } = await openSavedBookEditor({
attributesManager: newPanelStateManager,
parent: embeddable,
isCreate: true,
core,
});

const initialState: BookRuntimeState = await (async () => {
const bookAttributes = serializeBookAttributes(newPanelStateManager);
// if we're adding this to the library, we only need to return the by reference state.
if (addToLibrary) {
const savedBookId = await saveBookAttributes(undefined, bookAttributes);
return { savedBookId, ...bookAttributes };
}
return bookAttributes;
})();
const bookAttributes = serializeBookAttributes(newPanelStateManager);
const initialState: BookRuntimeState = savedBookId
? { savedBookId, ...bookAttributes }
: { ...bookAttributes };

embeddable.addNewPanel<BookRuntimeState>({
panelType: SAVED_BOOK_ID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,42 @@ import { OverlayRef } from '@kbn/core-mount-utils-browser';
import { i18n } from '@kbn/i18n';
import { tracksOverlays } from '@kbn/presentation-containers';
import {
apiHasParentApi,
apiHasInPlaceLibraryTransforms,
apiHasUniqueId,
useBatchedOptionalPublishingSubjects,
} from '@kbn/presentation-publishing';
import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import React, { useState } from 'react';
import { serializeBookAttributes } from './book_state';
import { BookAttributesManager } from './types';
import { BookApi, BookAttributesManager } from './types';
import { saveBookAttributes } from './saved_book_library';

export const openSavedBookEditor = (
attributesManager: BookAttributesManager,
isCreate: boolean,
core: CoreStart,
api: unknown
): Promise<{ addToLibrary: boolean }> => {
export const openSavedBookEditor = ({
attributesManager,
isCreate,
core,
parent,
api,
}: {
attributesManager: BookAttributesManager;
isCreate: boolean;
core: CoreStart;
parent?: unknown;
api?: BookApi;
}): Promise<{ savedBookId?: string }> => {
return new Promise((resolve) => {
const closeOverlay = (overlayRef: OverlayRef) => {
if (apiHasParentApi(api) && tracksOverlays(api.parentApi)) {
api.parentApi.clearOverlays();
}
if (tracksOverlays(parent)) parent.clearOverlays();
overlayRef.close();
};

const initialState = serializeBookAttributes(attributesManager);
const overlay = core.overlays.openFlyout(
toMountPoint(
<SavedBookEditor
attributesManager={attributesManager}
api={api}
isCreate={isCreate}
attributesManager={attributesManager}
onCancel={() => {
// set the state back to the initial state and reject
attributesManager.authorName.next(initialState.authorName);
Expand All @@ -64,16 +71,23 @@ export const openSavedBookEditor = (
attributesManager.numberOfPages.next(initialState.numberOfPages);
closeOverlay(overlay);
}}
onSubmit={(addToLibrary: boolean) => {
onSubmit={async (addToLibrary: boolean) => {
const savedBookId = addToLibrary
? await saveBookAttributes(
apiHasInPlaceLibraryTransforms(api) ? api.libraryId$.value : undefined,
serializeBookAttributes(attributesManager)
)
: undefined;

closeOverlay(overlay);
resolve({ addToLibrary });
resolve({ savedBookId });
}}
/>,
core
),
{
type: isCreate ? 'overlay' : 'push',
size: isCreate ? 'm' : 's',
size: 'm',
onClose: () => closeOverlay(overlay),
}
);
Expand All @@ -83,9 +97,7 @@ export const openSavedBookEditor = (
* if our parent needs to know about the overlay, notify it. This allows the parent to close the overlay
* when navigating away, or change certain behaviors based on the overlay being open.
*/
if (apiHasParentApi(api) && tracksOverlays(api.parentApi)) {
api.parentApi.openOverlay(overlay, overlayOptions);
}
if (tracksOverlays(parent)) parent.openOverlay(overlay, overlayOptions);
});
};

Expand All @@ -94,19 +106,24 @@ export const SavedBookEditor = ({
isCreate,
onSubmit,
onCancel,
api,
}: {
attributesManager: BookAttributesManager;
isCreate: boolean;
onSubmit: (addToLibrary: boolean) => void;
onSubmit: (addToLibrary: boolean) => Promise<void>;
onCancel: () => void;
api?: BookApi;
}) => {
const [addToLibrary, setAddToLibrary] = React.useState(false);
const [authorName, synopsis, bookTitle, numberOfPages] = useBatchedOptionalPublishingSubjects(
attributesManager.authorName,
attributesManager.bookSynopsis,
attributesManager.bookTitle,
attributesManager.numberOfPages
);
const [libraryId, authorName, synopsis, bookTitle, numberOfPages] =
useBatchedOptionalPublishingSubjects(
api?.libraryId$,
attributesManager.authorName,
attributesManager.bookSynopsis,
attributesManager.bookTitle,
attributesManager.numberOfPages
);
const [addToLibrary, setAddToLibrary] = useState(Boolean(libraryId));
const [saving, setSaving] = useState(false);

return (
<>
Expand All @@ -130,6 +147,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldText
disabled={saving}
value={authorName ?? ''}
onChange={(e) => attributesManager.authorName.next(e.target.value)}
/>
Expand All @@ -140,6 +158,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldText
disabled={saving}
value={bookTitle ?? ''}
onChange={(e) => attributesManager.bookTitle.next(e.target.value)}
/>
Expand All @@ -150,6 +169,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldNumber
disabled={saving}
value={numberOfPages ?? ''}
onChange={(e) => attributesManager.numberOfPages.next(+e.target.value)}
/>
Expand All @@ -160,6 +180,7 @@ export const SavedBookEditor = ({
})}
>
<EuiTextArea
disabled={saving}
value={synopsis ?? ''}
onChange={(e) => attributesManager.bookSynopsis.next(e.target.value)}
/>
Expand All @@ -168,27 +189,33 @@ export const SavedBookEditor = ({
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={onCancel} flush="left">
<EuiButtonEmpty disabled={saving} iconType="cross" onClick={onCancel} flush="left">
{i18n.translate('embeddableExamples.savedBook.editor.cancel', {
defaultMessage: 'Discard changes',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
{isCreate && (
<EuiFlexItem grow={false}>
<EuiSwitch
label={i18n.translate('embeddableExamples.savedBook.editor.addToLibrary', {
defaultMessage: 'Save to library',
})}
checked={addToLibrary}
onChange={() => setAddToLibrary(!addToLibrary)}
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButton onClick={() => onSubmit(addToLibrary)} fill>
<EuiSwitch
label={i18n.translate('embeddableExamples.savedBook.editor.addToLibrary', {
defaultMessage: 'Save to library',
})}
checked={addToLibrary}
disabled={saving}
onChange={() => setAddToLibrary(!addToLibrary)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
isLoading={saving}
onClick={() => {
setSaving(true);
onSubmit(addToLibrary);
}}
fill
>
{isCreate
? i18n.translate('embeddableExamples.savedBook.editor.create', {
defaultMessage: 'Create book',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const saveBookAttributes = async (
maybeId?: string,
attributes?: BookAttributes
): Promise<string> => {
await new Promise((r) => setTimeout(r, 100)); // simulate save to network.
await new Promise((r) => setTimeout(r, 500)); // simulate save to network.
const id = maybeId ?? v4();
storage.set(id, attributes);
return id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,22 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
{
...titlesApi,
onEdit: async () => {
openSavedBookEditor(bookAttributesManager, false, core, api);
openSavedBookEditor({
attributesManager: bookAttributesManager,
parent: api.parentApi,
isCreate: false,
core,
api,
}).then((result) => {
savedBookId$.next(result.savedBookId);
});
},
isEditingEnabled: () => true,
getTypeDisplayName: () =>
i18n.translate('embeddableExamples.savedbook.editBook.displayName', {
defaultMessage: 'book',
}),
serializeState: async () => {
serializeState: () => {
if (!Boolean(savedBookId$.value)) {
// if this book is currently by value, we serialize the entire state.
const bookByValueState: BookByValueSerializedState = {
Expand All @@ -98,16 +106,11 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
return { rawState: bookByValueState };
}

// if this book is currently by reference, we serialize the reference and write to the external store.
// if this book is currently by reference, we serialize the reference only.
const bookByReferenceState: BookByReferenceSerializedState = {
savedBookId: savedBookId$.value!,
...serializeTitles(),
};

await saveBookAttributes(
savedBookId$.value,
serializeBookAttributes(bookAttributesManager)
);
return { rawState: bookByReferenceState };
},

Expand Down
1 change: 0 additions & 1 deletion examples/embeddable_examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"@kbn/kibana-utils-plugin",
"@kbn/core-mount-utils-browser",
"@kbn/react-kibana-mount",
"@kbn/std",
"@kbn/shared-ux-router"
]
}
Loading

0 comments on commit abfd590

Please sign in to comment.