From e269d04ee097b8c56464066cad4477e04ed3daa8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 7 May 2025 13:32:33 -0600 Subject: [PATCH] [embeddable] cleanup usages of useBatchedOptionalPublishingSubjects (#216714) `useBatchedOptionalPublishingSubjects` should only be used when `api` is not available until after rendering. This PR replaces usages of `useBatchedOptionalPublishingSubjects` with `useBatchedPublishingSubjects` where possible. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- .../public/app/render_examples.tsx | 77 +++++++++++++------ .../publishing_subject/publishing_batcher.ts | 6 +- .../public/embeddable/links_embeddable.tsx | 4 +- .../presentation_panel_internal.tsx | 4 +- .../components/options_list_control.tsx | 12 ++- .../filters_notification_popover.tsx | 9 ++- .../search_embeddable_grid_component.tsx | 23 +++--- .../discover/public/embeddable/types.ts | 4 +- 8 files changed, 84 insertions(+), 55 deletions(-) diff --git a/examples/embeddable_examples/public/app/render_examples.tsx b/examples/embeddable_examples/public/app/render_examples.tsx index 96dccd976c0b..bbfda9025deb 100644 --- a/examples/embeddable_examples/public/app/render_examples.tsx +++ b/examples/embeddable_examples/public/app/render_examples.tsx @@ -22,51 +22,80 @@ import { } from '@elastic/eui'; import { BehaviorSubject, Subject } from 'rxjs'; import { TimeRange } from '@kbn/es-query'; -import { useBatchedOptionalPublishingSubjects } from '@kbn/presentation-publishing'; +import { PublishesDataLoading, useStateFromPublishingSubject } 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'; +function DatePicker({ + dataLoading$, + onReload, + timeRange, + setTimeRange, +}: { + dataLoading$: PublishesDataLoading['dataLoading$']; + timeRange: TimeRange; + setTimeRange: (timeRange: TimeRange) => void; + onReload: () => void; +}) { + const dataLoading = useStateFromPublishingSubject(dataLoading$); + return ( + { + setTimeRange({ + from: start, + to: end, + }); + }} + onRefresh={() => { + onReload(); + }} + /> + ); +} + export const RenderExamples = () => { const parentApi = useMemo(() => { + const timeRange$ = new BehaviorSubject({ + from: 'now-24h', + to: 'now', + }); + const reload$ = new Subject(); return { - reload$: new Subject(), + reload$, + onReload: () => { + reload$.next(); + }, getSerializedStateForChild: () => ({ rawState: { timeRange: undefined, }, }), - timeRange$: new BehaviorSubject({ - from: 'now-24h', - to: 'now', - }), + timeRange$, + setTimeRange: (timeRange: TimeRange | undefined) => { + if (timeRange) timeRange$.next(timeRange); + }, }; // only run onMount }, []); const [api, setApi] = useState(null); const [hidePanelChrome, setHidePanelChrome] = useState(false); - const [dataLoading, timeRange] = useBatchedOptionalPublishingSubjects( - api?.dataLoading$, - parentApi.timeRange$ - ); + const timeRange = useStateFromPublishingSubject(parentApi.timeRange$); return (
- { - parentApi.timeRange$.next({ - from: start, - to: end, - }); - }} - onRefresh={() => { - parentApi.reload$.next(); - }} - /> + {api && ( + + )} diff --git a/src/platform/packages/shared/presentation/presentation_publishing/publishing_subject/publishing_batcher.ts b/src/platform/packages/shared/presentation/presentation_publishing/publishing_subject/publishing_batcher.ts index fef592316247..f58374c9716e 100644 --- a/src/platform/packages/shared/presentation/presentation_publishing/publishing_subject/publishing_batcher.ts +++ b/src/platform/packages/shared/presentation/presentation_publishing/publishing_subject/publishing_batcher.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { combineLatest, debounceTime, skip } from 'rxjs'; import { AnyPublishingSubject, PublishingSubject, UnwrapPublishingSubjectTuple } from './types'; @@ -25,6 +25,8 @@ const hasSubjectsArrayChanged = ( }; /** + * @deprecated use useBatchedPublishingSubjects instead. + * * Batches the latest values of multiple publishing subjects into a single object. Use this to avoid unnecessary re-renders. * Use when `subjects` may not be defined on initial component render. * @@ -59,7 +61,7 @@ export const useBatchedOptionalPublishingSubjects = < /** * Subscribe to all subjects and update the latest values when any of them change. */ - useEffect(() => { + useLayoutEffect(() => { if (!isFirstRender.current) { setLatestPublishedValues(unwrapPublishingSubjectArray(subjectsToUse)); } else { diff --git a/src/platform/plugins/private/links/public/embeddable/links_embeddable.tsx b/src/platform/plugins/private/links/public/embeddable/links_embeddable.tsx index 55f863a63017..48c636e0434f 100644 --- a/src/platform/plugins/private/links/public/embeddable/links_embeddable.tsx +++ b/src/platform/plugins/private/links/public/embeddable/links_embeddable.tsx @@ -18,7 +18,7 @@ import { SerializedTitles, initializeTitleManager, SerializedPanelState, - useBatchedOptionalPublishingSubjects, + useBatchedPublishingSubjects, initializeStateManager, titleComparators, } from '@kbn/presentation-publishing'; @@ -257,7 +257,7 @@ export const getLinksEmbeddableFactory = () => { }); const Component = () => { - const [links, layout] = useBatchedOptionalPublishingSubjects( + const [links, layout] = useBatchedPublishingSubjects( stateManager.api.links$, stateManager.api.layout$ ); diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx index 2f924b17e1b8..1c2aeada1f65 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_internal.tsx @@ -49,10 +49,10 @@ export const PresentationPanelInternal = < const dragHandles = useRef<{ [dragHandleKey: string]: HTMLElement | null }>({}); - const viewModeSubject = (() => { + const viewModeSubject = useMemo(() => { if (apiPublishesViewMode(api)) return api.viewMode$; if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) return api.parentApi.viewMode$; - })(); + }, [api]); const [ dataLoading, diff --git a/src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/components/options_list_control.tsx b/src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/components/options_list_control.tsx index 5b8e34b603a4..4c280ad524dc 100644 --- a/src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/components/options_list_control.tsx +++ b/src/platform/plugins/shared/controls/public/controls/data_controls/options_list_control/components/options_list_control.tsx @@ -20,11 +20,9 @@ import { EuiToolTip, htmlIdGenerator, } from '@elastic/eui'; -import { - useBatchedOptionalPublishingSubjects, - useBatchedPublishingSubjects, -} from '@kbn/presentation-publishing'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; import { isCompressed } from '../../../../control_group/utils/is_compressed'; import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; import { MIN_POPOVER_WIDTH } from '../../../constants'; @@ -52,6 +50,7 @@ export const OptionsListControl = ({ loading, panelTitle, fieldFormatter, + defaultPanelTitle, ] = useBatchedPublishingSubjects( stateManager.exclude, stateManager.existsSelected, @@ -60,11 +59,10 @@ export const OptionsListControl = ({ api.field$, api.dataLoading$, api.title$, - api.fieldFormatter + api.fieldFormatter, + api.defaultTitle$ ?? new BehaviorSubject(undefined) ); - const [defaultPanelTitle] = useBatchedOptionalPublishingSubjects(api.defaultTitle$); - const delimiter = useMemo(() => OptionsListStrings.control.getSeparator(field?.type), [field]); const { hasSelections, selectionDisplayNode, selectedOptionsCount } = useMemo(() => { diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_popover.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_popover.tsx index 99a940623693..5369b202f448 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_popover.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_popover.tsx @@ -29,10 +29,11 @@ import { EmbeddableApiContext, apiCanLockHoverActions, getViewModeSubject, - useBatchedOptionalPublishingSubjects, + useBatchedPublishingSubjects, } from '@kbn/presentation-publishing'; import { ActionExecutionMeta } from '@kbn/ui-actions-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { BehaviorSubject } from 'rxjs'; import { uiActionsService } from '../services/kibana_services'; import { dashboardFilterNotificationActionStrings } from './_dashboard_actions_strings'; import { FiltersNotificationActionApi } from './filters_notification_action'; @@ -77,9 +78,9 @@ export function FiltersNotificationPopover({ api }: { api: FiltersNotificationAc } }, [api, setDisableEditButton]); - const [dataViews, parentViewMode] = useBatchedOptionalPublishingSubjects( - api.parentApi?.dataViews$, - getViewModeSubject(api ?? undefined) + const [dataViews, parentViewMode] = useBatchedPublishingSubjects( + api.parentApi?.dataViews$ ?? new BehaviorSubject(undefined), + getViewModeSubject(api) ?? new BehaviorSubject(undefined) ); const showEditButton = !disableEditbutton && parentViewMode === 'edit' && canEditUnifiedSearch; diff --git a/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx index 48d7324f38f4..49efc27cd011 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -13,10 +13,7 @@ import type { BehaviorSubject } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils'; import type { FetchContext } from '@kbn/presentation-publishing'; -import { - useBatchedOptionalPublishingSubjects, - useBatchedPublishingSubjects, -} from '@kbn/presentation-publishing'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; import type { SearchResponseIncompleteWarning } from '@kbn/search-response-warnings/src/types'; import type { DataGridDensity } from '@kbn/unified-data-table'; @@ -70,6 +67,10 @@ export function SearchEmbeddableGridComponent({ totalHitCount, columnsMeta, grid, + panelTitle, + panelDescription, + savedSearchTitle, + savedSearchDescription, ] = useBatchedPublishingSubjects( api.dataLoading$, api.savedSearch$, @@ -81,7 +82,11 @@ export function SearchEmbeddableGridComponent({ stateManager.rows, stateManager.totalHitCount, stateManager.columnsMeta, - stateManager.grid + stateManager.grid, + api.title$, + api.description$, + api.defaultTitle$, + api.defaultDescription$ ); // `api.query$` and `api.filters$` are the initial values from the saved search SO (as of now) @@ -90,14 +95,6 @@ export function SearchEmbeddableGridComponent({ const savedSearchQuery = apiQuery; const savedSearchFilters = apiFilters; - const [panelTitle, panelDescription, savedSearchTitle, savedSearchDescription] = - useBatchedOptionalPublishingSubjects( - api.title$, - api.description$, - api.defaultTitle$, - api.defaultDescription$ - ); - const isEsql = useMemo(() => isEsqlMode(savedSearch), [savedSearch]); const sort = useMemo(() => { diff --git a/src/platform/plugins/shared/discover/public/embeddable/types.ts b/src/platform/plugins/shared/discover/public/embeddable/types.ts index 8c0827a8a81a..bcf259e317ac 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/types.ts +++ b/src/platform/plugins/shared/discover/public/embeddable/types.ts @@ -17,6 +17,7 @@ import type { HasSupportedTriggers, PublishesBlockingError, PublishesDataLoading, + PublishesDescription, PublishesSavedObjectId, PublishesWritableTitle, PublishesWritableUnifiedSearch, @@ -102,7 +103,8 @@ export type SearchEmbeddableApi = DefaultEmbeddableApi & + Required & PublishesSavedSearch & PublishesWritableDataViews & PublishesWritableUnifiedSearch &