Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { i18n } from '@kbn/i18n';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import {
initializeUnsavedChanges,
initializeTimeRangeManager,
initializeTitleManager,
timeRangeComparators,
Expand All @@ -38,7 +37,7 @@ export const getDataTableFactory = (
services: StartDeps
): EmbeddableFactory<DataTableSerializedState, DataTableApi> => ({
type: DATA_TABLE_ID,
buildEmbeddable: async ({ initialState, finalizeApi, parentApi, uuid }) => {
buildEmbeddable: async ({ initialState, linkToContainerState, finalizeApi, parentApi, uuid }) => {
const state = initialState;
const timeRangeManager = initializeTimeRangeManager(state);
const dataLoading$ = new BehaviorSubject<boolean | undefined>(true);
Expand All @@ -60,9 +59,7 @@ export const getDataTableFactory = (
};
};

const unsavedChangesApi = initializeUnsavedChanges<DataTableSerializedState>({
uuid,
parentApi,
const containerLinkApi = linkToContainerState({
serializeState,
anyStateChange$: merge(titleManager.anyStateChange$, timeRangeManager.anyStateChange$),
getComparators: () => {
Expand All @@ -71,18 +68,17 @@ export const getDataTableFactory = (
...timeRangeComparators,
};
},
onReset: (lastSaved) => {
timeRangeManager.reinitializeState(lastSaved);
titleManager.reinitializeState(lastSaved);
applySerializedState: (nextState) => {
timeRangeManager.reinitializeState(nextState);
titleManager.reinitializeState(nextState);
},
});

const api = finalizeApi({
...timeRangeManager.api,
...titleManager.api,
...unsavedChangesApi,
...containerLinkApi,
dataLoading$,
serializeState,
});

const queryService = await initializeDataTableQueries(services, api, dataLoading$);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
initializeTitleManager,
titleComparators,
useBatchedPublishingSubjects,
initializeUnsavedChanges,
} from '@kbn/presentation-publishing';
import { LazyDataViewPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
import {
Expand Down Expand Up @@ -85,7 +84,7 @@ export const getFieldListFactory = (
) => {
const fieldListEmbeddableFactory: EmbeddableFactory<FieldListSerializedState, FieldListApi> = {
type: FIELD_LIST_ID,
buildEmbeddable: async ({ initialState, finalizeApi, parentApi, uuid }) => {
buildEmbeddable: async ({ initialState, finalizeApi, linkToContainerState }) => {
const state = await deserializeState(dataViews, initialState);
const allDataViews = await dataViews.getIdsWithTitle();
const subscriptions = new Subscription();
Expand Down Expand Up @@ -117,19 +116,17 @@ export const getFieldListFactory = (
};
}

const unsavedChangesApi = initializeUnsavedChanges<FieldListSerializedState>({
uuid,
parentApi,
serializeState,
const containerStateApi = linkToContainerState({
anyStateChange$: merge(titleManager.anyStateChange$, fieldListStateManager.anyStateChange$),
getComparators: () => ({
...titleComparators,
selectedFieldNames: (a, b) => {
selectedFieldNames: (a: string[] | undefined, b: string[] | undefined) => {
return (a?.slice().sort().join(',') ?? '') === (b?.slice().sort().join(',') ?? '');
},
dataViewId: 'referenceEquality',
}),
onReset: async (lastSaved) => {
serializeState,
applySerializedState: async (lastSaved: FieldListSerializedState | undefined) => {
const lastState = await deserializeState(dataViews, lastSaved);
fieldListStateManager.reinitializeState(lastState);
titleManager.reinitializeState(lastSaved);
Expand All @@ -138,8 +135,7 @@ export const getFieldListFactory = (

const api = finalizeApi({
...titleManager.api,
...unsavedChangesApi,
serializeState,
...containerStateApi,
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
initializeStateManager,
titleComparators,
apiIsPresentationContainer,
initializeUnsavedChanges,
} from '@kbn/presentation-publishing';
import React from 'react';
import { merge } from 'rxjs';
Expand All @@ -50,7 +49,7 @@ const bookStateComparators: StateComparators<BookState> = {
export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
const savedBookEmbeddableFactory: EmbeddableFactory<BookEmbeddableState, BookApi> = {
type: BOOK_EMBEDDABLE_TYPE,
buildEmbeddable: async ({ initialState, finalizeApi, parentApi, uuid }) => {
buildEmbeddable: async ({ initialState, finalizeApi, linkToContainerState }) => {
const titleManager = initializeTitleManager(initialState);
const savedObjectId = (initialState as BookByReferenceState).savedObjectId;
const initialBookState = savedObjectId ? await loadBook(savedObjectId) : initialState;
Expand All @@ -65,12 +64,7 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
...(id ? { savedObjectId: id } : bookStateManager.getLatestState()),
});

const serializeState = () => serializeBook(savedObjectId);

const unsavedChangesApi = initializeUnsavedChanges<BookEmbeddableState>({
uuid,
parentApi,
serializeState,
const containerStateApi = linkToContainerState({
anyStateChange$: merge(titleManager.anyStateChange$, bookStateManager.anyStateChange$),
getComparators: () => {
return {
Expand All @@ -79,14 +73,15 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
savedObjectId: 'skip', // saved book id will not change over the lifetime of the embeddable.
};
},
onReset: async (lastSaved) => {
titleManager.reinitializeState(lastSaved);
if (!savedObjectId) bookStateManager.reinitializeState(lastSaved as BookState);
serializeState: () => serializeBook(savedObjectId),
applySerializedState: async (nextState) => {
titleManager.reinitializeState(nextState);
if (!savedObjectId) bookStateManager.reinitializeState(nextState as BookState);
},
});

const api = finalizeApi({
...unsavedChangesApi,
...containerStateApi,
...titleManager.api,
onEdit: async () => {
openLazyFlyout({
Expand Down Expand Up @@ -122,7 +117,6 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
i18n.translate('embeddableExamples.savedbook.editBook.displayName', {
defaultMessage: 'book',
}),
serializeState,

// library transforms
getSavedObjectId: () => savedObjectId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
initializeTimeRangeManager,
timeRangeComparators,
useBatchedPublishingSubjects,
initializeUnsavedChanges,
} from '@kbn/presentation-publishing';
import React, { useEffect } from 'react';
import { BehaviorSubject, switchMap, tap } from 'rxjs';
Expand All @@ -28,7 +27,7 @@ import type { SearchApi, Services, SearchSerializedState } from './types';
export const getSearchEmbeddableFactory = (services: Services) => {
const factory: EmbeddableFactory<SearchSerializedState, SearchApi> = {
type: SEARCH_EMBEDDABLE_TYPE,
buildEmbeddable: async ({ initialState, finalizeApi, parentApi, uuid }) => {
buildEmbeddable: async ({ initialState, finalizeApi, linkToContainerState }) => {
const timeRangeManager = initializeTimeRangeManager(initialState);
const defaultDataView = await services.dataViews.getDefaultDataView();
const dataViews$ = new BehaviorSubject<DataView[] | undefined>(
Expand All @@ -53,10 +52,7 @@ export const getSearchEmbeddableFactory = (services: Services) => {
};
}

const unsavedChangesApi = initializeUnsavedChanges({
uuid,
parentApi,
serializeState,
const containerStateApi = linkToContainerState({
anyStateChange$: timeRangeManager.anyStateChange$,
getComparators: () => {
/**
Expand All @@ -66,23 +62,23 @@ export const getSearchEmbeddableFactory = (services: Services) => {
*/
return timeRangeComparators;
},
onReset: (lastSaved) => {
serializeState,
applySerializedState: (nextState) => {
/**
* if this embeddable had a difference between its runtime and serialized state, we could run the 'deserializeState'
* function here before resetting. onReset can be async so to support a potential async deserialize function.
* function here before applying the state. onReset can be async to support a potential async deserialize function.
*/

timeRangeManager.reinitializeState(lastSaved);
timeRangeManager.reinitializeState(nextState);
},
});

const api = finalizeApi({
blockingError$,
dataViews$,
dataLoading$,
...unsavedChangesApi,
...containerStateApi,
...timeRangeManager.api,
serializeState,
});

const count$ = new BehaviorSubject<number>(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,11 @@ export {
type HasSerializedChildState,
} from './interfaces/containers/child_state';

export { childrenUnsavedChanges$ } from './interfaces/containers/unsaved_changes/children_unsaved_changes';

export { initializeUnsavedChanges } from './interfaces/containers/unsaved_changes/initialize_unsaved_changes';
export { childrenUnsavedChanges$ } from './interfaces/containers/container_state/children_unsaved_changes';
export {
linkToContainerState,
type ContainerStateManagerInitializer,
} from './interfaces/containers/container_state/link_to_container_state';

export {
apiCanDuplicatePanels,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,42 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { Observable } from 'rxjs';
import type { MaybePromise } from '@kbn/utility-types';
import { combineLatestWith, debounceTime, map, of } from 'rxjs';
import { type StateComparators, areComparatorsEqual } from '../../../state_manager';
import type { StateManager, StateManagerInitializer } from '../../../state_manager/types';
import type { HasParentApi } from '../../has_parent_api';
import type { HasSerializableState } from '../../has_serializable_state';
import type { HasUniqueId } from '../../has_uuid';
import type { PublishesUnsavedChanges } from '../../publishes_unsaved_changes';
import { type StateComparators, areComparatorsEqual } from '../../../state_manager';
import { getTitle } from '../../titles/publishes_title';
import { apiHasLastSavedChildState } from '../last_saved_child_state';
import type { PresentationContainer } from '../presentation_container';

const UNSAVED_CHANGES_DEBOUNCE = 100;

export const initializeUnsavedChanges = <StateType extends object = object>({
export interface ContainerStateManagerInitializer<StateType extends object>
extends HasSerializableState<StateType> {
defaultState?: StateManagerInitializer<StateType>['defaultState'];
anyStateChange$: StateManager<StateType>['anyStateChange$'];
getComparators: () => StateComparators<StateType>;
}

export const linkToContainerState = <StateType extends object = object>({
uuid,
onReset,
parentApi,
getComparators,
defaultState,
serializeState,
anyStateChange$,
checkRefEquality,
}: {
uuid: string;
parentApi: unknown;
anyStateChange$: Observable<void>;
serializeState: () => StateType;
getComparators: () => StateComparators<StateType>;
defaultState?: Partial<StateType>;
onReset?: (lastSavedPanelState?: StateType) => MaybePromise<void>;
checkRefEquality?: boolean;
}): PublishesUnsavedChanges & Pick<HasSerializableState<StateType>, 'applySerializedState'> => {
const applySerializedState = async (state?: StateType) => {
await onReset?.(state);
};

serializeState,
getComparators,
applySerializedState,
}: HasSerializableState<StateType> &
HasUniqueId &
HasParentApi &
ContainerStateManagerInitializer<StateType>): PublishesUnsavedChanges &
HasSerializableState<StateType> => {
if (!apiHasLastSavedChildState<StateType>(parentApi)) {
return {
serializeState,
applySerializedState,
hasUnsavedChanges$: of(false),
};
Expand Down Expand Up @@ -72,5 +72,9 @@ export const initializeUnsavedChanges = <StateType extends object = object>({
})
);

return { applySerializedState, hasUnsavedChanges$ };
return {
serializeState,
hasUnsavedChanges$,
applySerializedState,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { camelCase } from 'lodash';
import { BehaviorSubject, map, merge } from 'rxjs';
import { runComparator } from './state_comparators';
import type { StateComparators, StateManager, WithAllKeys } from './types';
import type { StateManager, StateManagerInitializer, WithAllKeys } from './types';

type SubjectOf<StateType extends object> = BehaviorSubject<WithAllKeys<StateType>[keyof StateType]>;

Expand All @@ -29,9 +29,9 @@ type KeyToSubjectMap<StateType extends object> = {
* @param comparators - Optional StateComparators. When provided, subject will only emit when value changes.
*/
export const initializeStateManager = <StateType extends object>(
initialState: Partial<StateType>,
defaultState: WithAllKeys<StateType>,
comparators?: StateComparators<StateType>
initialState: StateManagerInitializer<StateType>['initialState'],
defaultState: StateManagerInitializer<StateType>['defaultState'],
comparators?: StateManagerInitializer<StateType>['comparators']
): StateManager<StateType> => {
const allState = { ...defaultState, ...initialState };
const allSubjects: Array<SubjectOf<StateType>> = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import type { SnakeToCamelCase } from '../utils/types';

export type WithAllKeys<T extends object> = { [Key in keyof Required<T>]: T[Key] };

export interface StateManagerInitializer<StateType extends object> {
initialState: Partial<StateType>;
defaultState: WithAllKeys<StateType>;
comparators?: StateComparators<StateType>;
}

export type ComparatorFunction<StateType, KeyType extends keyof StateType> = (
last: StateType[KeyType] | undefined,
current: StateType[KeyType] | undefined,
Expand Down
Loading
Loading