From 1427c5f2e5385f0e3f1cbb82c067c5529796f4b3 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 9 Jan 2024 12:12:06 -0700 Subject: [PATCH] isolate publishing subject logic and rename methods --- .../interfaces/panel_management.ts | 4 +- .../presentation_publishing/index.ts | 6 +- .../interfaces/publishes_blocking_error.ts | 4 +- .../interfaces/publishes_data_loading.ts | 4 +- .../interfaces/publishes_data_views.ts | 4 +- .../publishes_disabled_action_ids.ts | 4 +- .../publishes_local_unified_search.ts | 8 +-- .../interfaces/publishes_panel_description.ts | 6 +- .../interfaces/publishes_panel_title.ts | 8 +-- .../interfaces/publishes_parent_api.ts | 4 +- .../interfaces/publishes_saved_object_id.ts | 4 +- .../interfaces/publishes_uuid.ts | 4 +- .../interfaces/publishes_view_mode.ts | 4 +- .../publishing_subject.ts | 60 +++++++++++++++++++ .../publishing_utils.ts | 52 +--------------- 15 files changed, 93 insertions(+), 83 deletions(-) create mode 100644 packages/presentation/presentation_publishing/publishing_subject.ts diff --git a/packages/presentation/presentation_containers/interfaces/panel_management.ts b/packages/presentation/presentation_containers/interfaces/panel_management.ts index 40d9fa9994c73..33dee5f1e2325 100644 --- a/packages/presentation/presentation_containers/interfaces/panel_management.ts +++ b/packages/presentation/presentation_containers/interfaces/panel_management.ts @@ -8,7 +8,7 @@ import { PublishingSubject, - useReactiveVarFromSubject, + useStateFromPublishingSubject, } from '@kbn/presentation-publishing/publishing_utils'; export interface CanDuplicatePanels { @@ -34,6 +34,6 @@ export const apiCanExpandPanels = (unknownApi: unknown | null): unknownApi is Ca * Gets this API's expanded panel state as a reactive variable which will cause re-renders on change. */ export const useExpandedPanelId = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( apiCanExpandPanels(api) ? api.expandedPanelId : undefined ); diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 3b3e555f0cfae..80ca9632f9986 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -95,8 +95,8 @@ export { } from './interfaces/publishes_view_mode'; export { useBatchedPublishingSubjects } from './publishing_batcher'; export { - useApiPublisher, - useReactiveVarFromSubject, - useSubjectFromReactiveVar, + useStateFromPublishingSubject, + usePublishingSubject, type PublishingSubject, } from './publishing_utils'; +export { useApiPublisher } from './publishing_utils'; diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_blocking_error.ts b/packages/presentation/presentation_publishing/interfaces/publishes_blocking_error.ts index 85107c901f75c..d730dfa053516 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_blocking_error.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_blocking_error.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesBlockingError { blockingError: PublishingSubject; @@ -22,6 +22,6 @@ export const apiPublishesBlockingError = ( * Gets this API's fatal error as a reactive variable which will cause re-renders on change. */ export const useBlockingError = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( api?.blockingError ); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_data_loading.ts b/packages/presentation/presentation_publishing/interfaces/publishes_data_loading.ts index cb3f134745be2..aee4aa9e3104d 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_data_loading.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_data_loading.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesDataLoading { dataLoading: PublishingSubject; @@ -22,6 +22,6 @@ export const apiPublishesDataLoading = ( * Gets this API's data loading state as a reactive variable which will cause re-renders on change. */ export const useDataLoading = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( apiPublishesDataLoading(api) ? api.dataLoading : undefined ); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts b/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts index b3ebef302fd96..3de83767cdf9d 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_data_views.ts @@ -7,7 +7,7 @@ */ import { DataView } from '@kbn/data-views-plugin/common'; -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesDataViews { dataViews: PublishingSubject; @@ -23,6 +23,6 @@ export const apiPublishesDataViews = ( * Gets this API's data views as a reactive variable which will cause re-renders on change. */ export const useDataViews = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( apiPublishesDataViews(api) ? api.dataViews : undefined ); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_disabled_action_ids.ts b/packages/presentation/presentation_publishing/interfaces/publishes_disabled_action_ids.ts index 074b806b572be..801d0debbdc84 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_disabled_action_ids.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_disabled_action_ids.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesDisabledActionIds { disabledActionIds: PublishingSubject; @@ -29,6 +29,6 @@ export const apiPublishesDisabledActionIds = ( * Gets this API's disabled action IDs as a reactive variable which will cause re-renders on change. */ export const useDisabledActionIds = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( api?.disabledActionIds ); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_local_unified_search.ts b/packages/presentation/presentation_publishing/interfaces/publishes_local_unified_search.ts index 60b831d34335f..3affc8ef0d216 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_local_unified_search.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_local_unified_search.ts @@ -7,7 +7,7 @@ */ import { TimeRange, Filter, Query, AggregateQuery } from '@kbn/es-query'; -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesLocalUnifiedSearch { isCompatibleWithLocalUnifiedSearch?: () => boolean; @@ -62,16 +62,16 @@ export const apiPublishesWritableLocalUnifiedSearch = ( * A hook that gets this API's local time range as a reactive variable which will cause re-renders on change. */ export const useLocalTimeRange = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.localTimeRange); + useStateFromPublishingSubject(api?.localTimeRange); /** * A hook that gets this API's local filters as a reactive variable which will cause re-renders on change. */ export const useLocalFilters = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.localFilters); + useStateFromPublishingSubject(api?.localFilters); /** * A hook that gets this API's local query as a reactive variable which will cause re-renders on change. */ export const useLocalQuery = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.localQuery); + useStateFromPublishingSubject(api?.localQuery); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_panel_description.ts b/packages/presentation/presentation_publishing/interfaces/publishes_panel_description.ts index 03cae70ce9faf..af68abf3b9664 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_panel_description.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_panel_description.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesPanelDescription { panelDescription: PublishingSubject; @@ -39,7 +39,7 @@ export const apiPublishesWritablePanelDescription = ( * A hook that gets this API's panel description as a reactive variable which will cause re-renders on change. */ export const usePanelDescription = (api: Partial | undefined) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( api?.panelDescription ); @@ -47,7 +47,7 @@ export const usePanelDescription = (api: Partial | un * A hook that gets this API's default panel description as a reactive variable which will cause re-renders on change. */ export const useDefaultPanelDescription = (api: Partial | undefined) => - useReactiveVarFromSubject< + useStateFromPublishingSubject< string | undefined, PublishesPanelDescription['defaultPanelDescription'] >(api?.defaultPanelDescription); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts index 72608a6a33e3f..92dbc9d04b594 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_panel_title.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesPanelTitle { panelTitle: PublishingSubject; @@ -46,16 +46,16 @@ export const apiPublishesWritablePanelTitle = ( * A hook that gets this API's panel title as a reactive variable which will cause re-renders on change. */ export const usePanelTitle = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.panelTitle); + useStateFromPublishingSubject(api?.panelTitle); /** * A hook that gets this API's hide panel title setting as a reactive variable which will cause re-renders on change. */ export const useHidePanelTitle = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.hidePanelTitle); + useStateFromPublishingSubject(api?.hidePanelTitle); /** * A hook that gets this API's default title as a reactive variable which will cause re-renders on change. */ export const useDefaultPanelTitle = (api: Partial | undefined) => - useReactiveVarFromSubject(api?.defaultPanelTitle); + useStateFromPublishingSubject(api?.defaultPanelTitle); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_parent_api.ts b/packages/presentation/presentation_publishing/interfaces/publishes_parent_api.ts index 2d29fc09678d7..51e7f3b8b97f9 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_parent_api.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_parent_api.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesParentApi { parentApi: PublishingSubject; @@ -30,6 +30,6 @@ export const useParentApi = < >( api: ApiType ): UnwrapParent => - useReactiveVarFromSubject( + useStateFromPublishingSubject( apiPublishesParentApi(api) ? api.parentApi : undefined ) as UnwrapParent; diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_saved_object_id.ts b/packages/presentation/presentation_publishing/interfaces/publishes_saved_object_id.ts index 584f13e51c111..8d03fc7474468 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_saved_object_id.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_saved_object_id.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; /** * This API publishes a saved object id which can be used to determine which saved object this API is linked to. @@ -28,4 +28,4 @@ export const apiPublishesSavedObjectId = ( * A hook that gets this API's saved object ID as a reactive variable which will cause re-renders on change. */ export const useSavedObjectId = (api: PublishesSavedObjectId | undefined) => - useReactiveVarFromSubject(api?.savedObjectId); + useStateFromPublishingSubject(api?.savedObjectId); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_uuid.ts b/packages/presentation/presentation_publishing/interfaces/publishes_uuid.ts index bb1add9ed9359..d3b170a99d3b7 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_uuid.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_uuid.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export interface PublishesUniqueId { uuid: PublishingSubject; @@ -26,6 +26,6 @@ export const useUniqueId = < >( api: ApiType ) => - useReactiveVarFromSubject( + useStateFromPublishingSubject( apiPublishesUniqueId(api) ? api.uuid : undefined ); diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_view_mode.ts b/packages/presentation/presentation_publishing/interfaces/publishes_view_mode.ts index befecfa8ef3c5..4c30f45bc4afe 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_view_mode.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_view_mode.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils'; +import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_utils'; export type ViewMode = 'view' | 'edit' | 'print' | 'preview'; @@ -48,4 +48,4 @@ export const useViewMode = < ApiType extends Partial = Partial >( api: ApiType | undefined -) => useReactiveVarFromSubject(api?.viewMode); +) => useStateFromPublishingSubject(api?.viewMode); diff --git a/packages/presentation/presentation_publishing/publishing_subject.ts b/packages/presentation/presentation_publishing/publishing_subject.ts new file mode 100644 index 0000000000000..4d07981d8725d --- /dev/null +++ b/packages/presentation/presentation_publishing/publishing_subject.ts @@ -0,0 +1,60 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { useEffect, useMemo, useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; + +/** + * A publishing subject is a RxJS subject that can be used to listen to value changes, but does not allow pushing values via the Next method. + */ +export type PublishingSubject = Omit, 'next'>; + +/** + * A utility type that makes a type optional if another passed in type is optional. + */ +type OptionalIfOptional = undefined extends TestType + ? Type | undefined + : Type; + +/** + * Declares a publishing subject, allowing external code to subscribe to react state changes. + * Changes to state fire subject.next + * @param state React state from useState hook. + */ +export const usePublishingSubject = ( + state: T +): PublishingSubject => { + const subject = useMemo>( + () => new BehaviorSubject(state), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + useEffect(() => subject.next(state), [subject, state]); + return subject; +}; + +/** + * Declares a state variable that is synced with a publishing subject value. + * @param subject Publishing subject. + */ +export const useStateFromPublishingSubject = < + ValueType extends unknown = unknown, + SubjectType extends PublishingSubject | undefined = + | PublishingSubject + | undefined +>( + subject?: SubjectType +): OptionalIfOptional => { + const [value, setValue] = useState(subject?.getValue()); + useEffect(() => { + if (!subject) return; + const subscription = subject.subscribe((newValue) => setValue(newValue)); + return () => subscription.unsubscribe(); + }, [subject]); + return value as OptionalIfOptional; +}; diff --git a/packages/presentation/presentation_publishing/publishing_utils.ts b/packages/presentation/presentation_publishing/publishing_utils.ts index e329189cc8b2d..37949a7d63ba6 100644 --- a/packages/presentation/presentation_publishing/publishing_utils.ts +++ b/packages/presentation/presentation_publishing/publishing_utils.ts @@ -6,57 +6,7 @@ * Side Public License, v 1. */ -import { useEffect, useImperativeHandle, useMemo, useState } from 'react'; -import { BehaviorSubject } from 'rxjs'; - -/** - * A publishing subject is a subject that can be used to listen to value changes, but does not allow pushing values via the Next method. - */ -export type PublishingSubject = Omit, 'next'>; - -/** - * A utility type that makes a type optional if another passed in type is optional. - */ -export type OptionalIfOptional = undefined extends TestType - ? Type | undefined - : Type; - -/** - * Transforms any reactive variable into a publishing subject that can be used by other components - * or actions to subscribe to changes in this piece of state. - */ -export const useSubjectFromReactiveVar = ( - value: T -): PublishingSubject => { - const subject = useMemo>( - () => new BehaviorSubject(value), - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - useEffect(() => subject.next(value), [subject, value]); - return subject; -}; - -/** - * Extracts a reactive variable from a publishing subject. If the type of the provided subject extends undefined, - * the returned value will be optional. - */ -export const useReactiveVarFromSubject = < - ValueType extends unknown = unknown, - SubjectType extends PublishingSubject | undefined = - | PublishingSubject - | undefined ->( - subject?: SubjectType -): OptionalIfOptional => { - const [value, setValue] = useState(subject?.getValue()); - useEffect(() => { - if (!subject) return; - const subscription = subject.subscribe((newValue) => setValue(newValue)); - return () => subscription.unsubscribe(); - }, [subject]); - return value as OptionalIfOptional; -}; +import { useImperativeHandle, useMemo } from 'react'; /** * Publishes any API to the passed in ref. Note that any API passed in will not be rebuilt on