From 2dc2e0d2e96064b28848c32bcf3664297d204ba4 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Wed, 31 Jul 2024 16:18:54 -0600 Subject: [PATCH] Draft of apply on reset - types still angry --- .../react_control_example.tsx | 4 +- .../control_group_unsaved_changes_api.ts | 16 +++++-- .../get_control_group_factory.tsx | 1 + .../react_controls/control_group/types.ts | 4 +- .../data_controls/initialize_data_control.ts | 42 ++++++++++++------- .../get_range_slider_control_factory.tsx | 2 +- .../get_search_control_factory.tsx | 2 +- .../react_controls/data_controls/types.ts | 3 ++ .../presentation_publishing/index.ts | 1 + .../interfaces/publishes_unsaved_changes.ts | 9 +++- .../public/react_embeddable_system/types.ts | 2 +- 11 files changed, 59 insertions(+), 27 deletions(-) diff --git a/examples/controls_example/public/app/react_control_example/react_control_example.tsx b/examples/controls_example/public/app/react_control_example/react_control_example.tsx index c3420cf22b6093..2163034252cb95 100644 --- a/examples/controls_example/public/app/react_control_example/react_control_example.tsx +++ b/examples/controls_example/public/app/react_control_example/react_control_example.tsx @@ -362,8 +362,8 @@ export const ReactControlExample = ({ { - controlGroupApi?.resetUnsavedChanges(); + onClick={async () => { + if (controlGroupApi) await controlGroupApi.asyncResetUnsavedChanges(); }} > Reset diff --git a/examples/controls_example/public/react_controls/control_group/control_group_unsaved_changes_api.ts b/examples/controls_example/public/react_controls/control_group/control_group_unsaved_changes_api.ts index 399fbf6c463cd6..4d79d4754750a5 100644 --- a/examples/controls_example/public/react_controls/control_group/control_group_unsaved_changes_api.ts +++ b/examples/controls_example/public/react_controls/control_group/control_group_unsaved_changes_api.ts @@ -14,12 +14,13 @@ import { } from '@kbn/presentation-containers'; import { apiPublishesUnsavedChanges, - PublishesUnsavedChanges, + PublishesAsyncUnsavedChanges, StateComparators, } from '@kbn/presentation-publishing'; import { combineLatest, map } from 'rxjs'; import { ControlsInOrder, getControlsInOrder } from './init_controls_manager'; import { ControlGroupRuntimeState, ControlPanelsState } from './types'; +import { DataControlApi } from '../data_controls/types'; export type ControlGroupComparatorState = Pick< ControlGroupRuntimeState, @@ -35,6 +36,7 @@ export type ControlGroupComparatorState = Pick< export function initializeControlGroupUnsavedChanges( children$: PresentationContainer['children$'], comparators: StateComparators, + applySelections: () => void, snapshotControlsRuntimeState: () => ControlPanelsState, parentApi: unknown, lastSavedRuntimeState: ControlGroupRuntimeState @@ -68,12 +70,20 @@ export function initializeControlGroupUnsavedChanges( return Object.keys(unsavedChanges).length ? unsavedChanges : undefined; }) ), - resetUnsavedChanges: () => { + asyncResetUnsavedChanges: async () => { controlGroupUnsavedChanges.api.resetUnsavedChanges(); + const filtersReadyPromises: Array> = []; Object.values(children$.value).forEach((controlApi) => { if (apiPublishesUnsavedChanges(controlApi)) controlApi.resetUnsavedChanges(); + if ((controlApi as DataControlApi).untilFiltersReady) { + filtersReadyPromises.push((controlApi as DataControlApi).untilFiltersReady()); + } }); + if (!comparators.autoApplySelections[0].getValue()) { + await Promise.all(filtersReadyPromises); + applySelections(); + } }, - } as PublishesUnsavedChanges, + } as PublishesAsyncUnsavedChanges, }; } diff --git a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx b/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx index d6c35963657bf0..8a7ea090fa3425 100644 --- a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx +++ b/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx @@ -113,6 +113,7 @@ export const getControlGroupEmbeddableFactory = (services: { ], labelPosition: [labelPosition$, (next: ControlStyle) => labelPosition$.next(next)], }, + selectionsManager.applySelections, controlsManager.snapshotControlsRuntimeState, parentApi, lastSavedRuntimeState diff --git a/examples/controls_example/public/react_controls/control_group/types.ts b/examples/controls_example/public/react_controls/control_group/types.ts index 9dfed915d1e73d..405988aa7238eb 100644 --- a/examples/controls_example/public/react_controls/control_group/types.ts +++ b/examples/controls_example/public/react_controls/control_group/types.ts @@ -19,11 +19,11 @@ import { import { HasEditCapabilities, HasParentApi, + PublishesAsyncUnsavedChanges, PublishesDataLoading, PublishesFilters, PublishesTimeslice, PublishesUnifiedSearch, - PublishesUnsavedChanges, PublishingSubject, } from '@kbn/presentation-publishing'; import { PublishesDataViews } from '@kbn/presentation-publishing/interfaces/publishes_data_views'; @@ -55,7 +55,7 @@ export type ControlGroupApi = PresentationContainer & HasSerializedChildState & HasEditCapabilities & PublishesDataLoading & - PublishesUnsavedChanges & + PublishesAsyncUnsavedChanges & PublishesControlGroupDisplaySettings & PublishesTimeslice & Partial & HasSaveNotification> & { diff --git a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts b/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts index 1055047779f711..7eee6222c527d0 100644 --- a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts +++ b/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts @@ -7,7 +7,7 @@ */ import { isEqual } from 'lodash'; -import { BehaviorSubject, combineLatest, first, switchMap } from 'rxjs'; +import { BehaviorSubject, combineLatest, filter, first, switchMap, tap } from 'rxjs'; import { CoreStart } from '@kbn/core-lifecycle-browser'; import { DataView, DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; @@ -39,7 +39,6 @@ export const initializeDataControl = ( comparators: StateComparators; stateManager: ControlStateManager; serialize: () => SerializedPanelState; - untilFiltersInitialized: () => Promise; } => { const defaultControl = initializeDefaultControlApi(state); @@ -49,6 +48,7 @@ export const initializeDataControl = ( const fieldName = new BehaviorSubject(state.fieldName); const dataViews = new BehaviorSubject(undefined); const filters$ = new BehaviorSubject(undefined); + const filtersReady$ = new BehaviorSubject(false); const stateManager: ControlStateManager = { ...defaultControl.stateManager, @@ -65,6 +65,10 @@ export const initializeDataControl = ( const dataViewIdSubscription = dataViewId .pipe( + tap(() => { + // need to fetch new data view before filters are ready to be published + filtersReady$.next(false); + }), switchMap(async (currentDataViewId) => { let dataView: DataView | undefined; try { @@ -153,6 +157,17 @@ export const initializeDataControl = ( }); }; + /** When the filter outputs **for the first time**, set `filtersReady` to `true` */ + let filtersInitialized = false; + combineLatest([defaultControl.api.blockingError, filters$]) + .pipe( + first(([blockingError, filters]) => blockingError !== undefined || (filters?.length ?? 0) > 0) + ) + .subscribe(() => { + filtersReady$.next(true); + filtersInitialized = true; + }); + const api: ControlApiInitialization = { ...defaultControl.api, panelTitle, @@ -160,10 +175,19 @@ export const initializeDataControl = ( dataViews, onEdit, filters$, + filtersReady$, setOutputFilter: (newFilter: Filter | undefined) => { filters$.next(newFilter ? [newFilter] : undefined); + if (filtersInitialized) filtersReady$.next(true); }, isEditingEnabled: () => true, + untilFiltersReady: async () => { + return new Promise((resolve) => { + filtersReady$.pipe(first((filtersReady) => filtersReady)).subscribe((ready) => { + resolve(); + }); + }); + }, }; return { @@ -196,19 +220,5 @@ export const initializeDataControl = ( ], }; }, - untilFiltersInitialized: async () => { - return new Promise((resolve) => { - combineLatest([defaultControl.api.blockingError, filters$]) - .pipe( - first( - ([blockingError, filters]) => - blockingError !== undefined || (filters?.length ?? 0) > 0 - ) - ) - .subscribe(() => { - resolve(); - }); - }); - }, }; }; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx index f76abf6bf0f8f9..8dfce004ac113f 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx +++ b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx @@ -201,7 +201,7 @@ export const getRangesliderControlFactory = ( }); if (initialState.value !== undefined) { - await dataControl.untilFiltersInitialized(); + await dataControl.api.untilFiltersReady(); } return { diff --git a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx index c7301882786435..eb404bdf74c0b4 100644 --- a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx +++ b/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx @@ -184,7 +184,7 @@ export const getSearchControlFactory = ({ }); if (initialState.searchString?.length) { - await dataControl.untilFiltersInitialized(); + await dataControl.api.untilFiltersReady(); } return { diff --git a/examples/controls_example/public/react_controls/data_controls/types.ts b/examples/controls_example/public/react_controls/data_controls/types.ts index b3379889f4223a..fa56ba3fe7f5c1 100644 --- a/examples/controls_example/public/react_controls/data_controls/types.ts +++ b/examples/controls_example/public/react_controls/data_controls/types.ts @@ -14,6 +14,7 @@ import { PublishesFilters, PublishesPanelTitle, } from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; import { ControlFactory, DefaultControlApi, DefaultControlState } from '../types'; export type DataControlApi = DefaultControlApi & @@ -21,6 +22,8 @@ export type DataControlApi = DefaultControlApi & HasEditCapabilities & PublishesDataViews & PublishesFilters & { + filtersReady$: BehaviorSubject; + untilFiltersReady: () => Promise; setOutputFilter: (filter: Filter | undefined) => void; // a control should only ever output a **single** filter }; diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 8d90462c8a1bd7..d32d71c5a7d744 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -110,6 +110,7 @@ export { export { apiPublishesUnsavedChanges, type PublishesUnsavedChanges, + type PublishesAsyncUnsavedChanges, } from './interfaces/publishes_unsaved_changes'; export { apiPublishesViewMode, diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts b/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts index 626959f41a9415..0db56bd873c9af 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts @@ -13,7 +13,14 @@ export interface PublishesUnsavedChanges { resetUnsavedChanges: () => void; } -export const apiPublishesUnsavedChanges = (api: unknown): api is PublishesUnsavedChanges => { +export interface PublishesAsyncUnsavedChanges + extends Pick, 'unsavedChanges'> { + asyncResetUnsavedChanges: () => Promise; +} + +export const apiPublishesUnsavedChanges = ( + api: unknown +): api is PublishesUnsavedChanges | PublishesAsyncUnsavedChanges => { return Boolean( api && (api as PublishesUnsavedChanges).unsavedChanges && diff --git a/src/plugins/embeddable/public/react_embeddable_system/types.ts b/src/plugins/embeddable/public/react_embeddable_system/types.ts index e345f5a131fc73..8973cef9ce109d 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/types.ts +++ b/src/plugins/embeddable/public/react_embeddable_system/types.ts @@ -126,6 +126,6 @@ export interface ReactEmbeddableFactory< * Last saved runtime state. Different from initialRuntimeState in that it does not contain previous sessions's unsaved changes * Compare with initialRuntimeState to flag unsaved changes on load */ - lastSavedRuntimeState: RuntimeState, + lastSavedRuntimeState: RuntimeState ) => Promise<{ Component: React.FC<{}>; api: Api }>; }