From 0fb26eafbf7f9362d97c976c3be451fd746f05a0 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 20 Feb 2025 13:38:54 -0700 Subject: [PATCH] [ui_actions] replace subscribeToCompatibilityChanges with getCompatibilityChangesSubject (#210693) Closes https://github.com/elastic/kibana/issues/206104 PR replaces `action.subscribeToCompatibilityChanges` with `action.getCompatibilityChangesSubject` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine Co-authored-by: Devon Thomson --- .../public/deprecation_badge.ts | 13 ++--- .../custom_time_range_badge.test.ts | 11 ++-- .../custom_time_range_badge.tsx | 13 ++--- .../edit_panel_action.test.tsx | 11 ++-- .../edit_panel_action/edit_panel_action.ts | 17 ++---- .../presentation_panel_hover_actions.tsx | 56 ++++++++++++------- .../use_presentation_panel_header_actions.tsx | 43 ++++++++++---- .../presentation_panel_error_internal.tsx | 26 +++++---- .../actions/clear_control_action.test.tsx | 13 ++--- .../public/actions/clear_control_action.tsx | 14 ++--- .../components/floating_actions.tsx | 16 ++++-- .../expand_panel_action.test.tsx | 11 ++-- .../dashboard_actions/expand_panel_action.tsx | 17 +++--- .../filters_notification_action.test.tsx | 20 ++++--- .../filters_notification_action.tsx | 9 +-- .../ui_actions/public/actions/action.ts | 20 ++----- .../public/actions/action_internal.ts | 6 +- .../public/service/ui_actions_service.ts | 2 +- .../open_in_discover_action.ts | 15 ++--- 19 files changed, 177 insertions(+), 156 deletions(-) diff --git a/src/platform/plugins/private/input_control_vis/public/deprecation_badge.ts b/src/platform/plugins/private/input_control_vis/public/deprecation_badge.ts index 1b96eda7fe293..278c6f31e7465 100644 --- a/src/platform/plugins/private/input_control_vis/public/deprecation_badge.ts +++ b/src/platform/plugins/private/input_control_vis/public/deprecation_badge.ts @@ -19,6 +19,7 @@ import { import { Action } from '@kbn/ui-actions-plugin/public'; import { apiHasVisualizeConfig, HasVisualizeConfig } from '@kbn/visualizations-plugin/public'; +import { map } from 'rxjs'; import { INPUT_CONTROL_VIS_TYPE } from './input_control_vis_type'; const ACTION_DEPRECATION_BADGE = 'ACTION_INPUT_CONTROL_DEPRECATION_BADGE'; @@ -66,14 +67,10 @@ export class InputControlDeprecationBadge implements Action) => void - ) { - if (!isApiCompatible(embeddable)) return; - return getViewModeSubject(embeddable)?.subscribe(() => { - onChange(compatibilityCheck(embeddable), this); - }); + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { + return isApiCompatible(embeddable) + ? getViewModeSubject(embeddable)?.pipe(map(() => undefined)) + : undefined; } public async execute() { diff --git a/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.test.ts b/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.test.ts index b7915d5abc358..509fb7fc968ef 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.test.ts +++ b/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.test.ts @@ -10,7 +10,7 @@ import { Filter, TimeRange, type AggregateQuery, type Query } from '@kbn/es-query'; import { PublishesUnifiedSearch } from '@kbn/presentation-publishing'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { CustomTimeRangeBadge } from './custom_time_range_badge'; const mockTimeRange: TimeRange = { from: 'now-17m', to: 'now' }; @@ -45,11 +45,12 @@ describe('custom time range badge action', () => { expect(await action.isCompatible(emptyContext)).toBe(false); }); - it('calls onChange when time range changes', () => { - const onChange = jest.fn(); + it('getCompatibilityChangesSubject emits when time range changes', (done) => { updateTimeRange(mockTimeRange); - action.subscribeToCompatibilityChanges(context, onChange); + const subject = action.getCompatibilityChangesSubject(context); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); updateTimeRange(undefined); - expect(onChange).toHaveBeenCalledWith(false, action); }); }); diff --git a/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.tsx b/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.tsx index 506c4c10d0424..19f7f1aa6ef5f 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_actions/customize_panel_action/custom_time_range_badge.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { apiPublishesTimeRange, EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { map } from 'rxjs'; import { ACTION_CUSTOMIZE_PANEL, CUSTOM_TIME_RANGE_BADGE } from './constants'; import { core, uiActions } from '../../kibana_services'; @@ -58,14 +59,10 @@ export class CustomTimeRangeBadge return apiPublishesTimeRange(embeddable); } - public subscribeToCompatibilityChanges( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: CustomTimeRangeBadge) => void - ) { - if (!apiPublishesTimeRange(embeddable)) return; - return embeddable.timeRange$.subscribe((timeRange) => { - onChange(Boolean(timeRange), this); - }); + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { + return apiPublishesTimeRange(embeddable) + ? embeddable.timeRange$.pipe(map(() => undefined)) + : undefined; } public async execute(context: ActionExecutionMeta & EmbeddableApiContext) { diff --git a/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.test.tsx b/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.test.tsx index 2c7447baf5006..52c3124a385c4 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.test.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.test.tsx @@ -8,7 +8,7 @@ */ import { PublishesViewMode, ViewMode } from '@kbn/presentation-publishing'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { EditPanelAction, EditPanelActionApi } from './edit_panel_action'; describe('Edit panel action', () => { @@ -63,10 +63,11 @@ describe('Edit panel action', () => { expect(await action.getHref(context)).toBe(href); }); - it('calls onChange when view mode changes', () => { - const onChange = jest.fn(); - action.subscribeToCompatibilityChanges(context, onChange); + it('getCompatibilityChangesSubject emits when view mode changes', (done) => { + const subject = action.getCompatibilityChangesSubject(context); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); setViewMode('view'); - expect(onChange).toHaveBeenCalledWith(false, action); }); }); diff --git a/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts b/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts index 9e1efc2ec928b..a65f52c3ed8ef 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts +++ b/src/platform/plugins/private/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts @@ -23,6 +23,7 @@ import { FrequentCompatibilityChangeAction, IncompatibleActionError, } from '@kbn/ui-actions-plugin/public'; +import { map } from 'rxjs'; import { ACTION_EDIT_PANEL } from './constants'; export type EditPanelActionApi = CanAccessViewMode & HasEditCapabilities; @@ -50,18 +51,10 @@ export class EditPanelAction }); } - public subscribeToCompatibilityChanges( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: Action) => void - ) { - if (!isApiCompatible(embeddable)) return; - return getViewModeSubject(embeddable)?.subscribe((viewMode) => { - if (viewMode === 'edit' && isApiCompatible(embeddable) && embeddable.isEditingEnabled()) { - onChange(true, this); - return; - } - onChange(false, this); - }); + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { + return isApiCompatible(embeddable) + ? getViewModeSubject(embeddable)?.pipe(map(() => undefined)) + : undefined; } public couldBecomeCompatible({ embeddable }: EmbeddableApiContext) { diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_hover_actions.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_hover_actions.tsx index e258c21e003cd..bf26a12508526 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_hover_actions.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/presentation_panel_hover_actions.tsx @@ -41,7 +41,7 @@ import { ViewMode, } from '@kbn/presentation-publishing'; import { ActionWithContext } from '@kbn/ui-actions-plugin/public/context_menu/build_eui_context_menu_panels'; -import { Subscription } from 'rxjs'; +import { Subscription, switchMap } from 'rxjs'; import { uiActions } from '../../kibana_services'; import { CONTEXT_MENU_TRIGGER, @@ -188,17 +188,24 @@ export const PresentationPanelHoverActions = ({ for (const frequentlyChangingAction of frequentlyChangingActions) { if ((quickActionIds as readonly string[]).includes(frequentlyChangingAction.id)) { - subscriptions.add( - frequentlyChangingAction.subscribeToCompatibilityChanges( - apiContext, - (isCompatible, action) => - handleActionCompatibilityChange( - 'quickActions', - isCompatible, - action as AnyApiAction - ) + const compatibilitySubscription = frequentlyChangingAction + .getCompatibilityChangesSubject(apiContext) + ?.pipe( + switchMap(async () => { + return await frequentlyChangingAction.isCompatible({ + ...apiContext, + trigger: contextMenuTrigger, + }); + }) ) - ); + .subscribe(async (isCompatible) => { + handleActionCompatibilityChange( + 'quickActions', + isCompatible, + frequentlyChangingAction as AnyApiAction + ); + }); + subscriptions.add(compatibilitySubscription); } } @@ -214,17 +221,24 @@ export const PresentationPanelHoverActions = ({ if ( (ALLOWED_NOTIFICATIONS as readonly string[]).includes(frequentlyChangingNotification.id) ) { - subscriptions.add( - frequentlyChangingNotification.subscribeToCompatibilityChanges( - apiContext, - (isCompatible, action) => - handleActionCompatibilityChange( - 'notifications', - isCompatible, - action as AnyApiAction - ) + const compatibilitySubscription = frequentlyChangingNotification + .getCompatibilityChangesSubject(apiContext) + ?.pipe( + switchMap(async () => { + return await frequentlyChangingNotification.isCompatible({ + ...apiContext, + trigger: panelNotificationTrigger, + }); + }) ) - ); + .subscribe(async (isCompatible) => { + handleActionCompatibilityChange( + 'notifications', + isCompatible, + frequentlyChangingNotification as AnyApiAction + ); + }); + subscriptions.add(compatibilitySubscription); } } })(); diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx index d5655273f3f1e..a8a0edb7d7245 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx @@ -9,7 +9,7 @@ import { EuiBadge, EuiNotificationBadge, EuiToolTip, useEuiTheme } from '@elastic/eui'; import React, { useEffect, useMemo, useState } from 'react'; -import { Subscription } from 'rxjs'; +import { Subscription, switchMap } from 'rxjs'; import { uiActions } from '../../kibana_services'; import { @@ -86,11 +86,20 @@ export const usePresentationPanelHeaderActions = < ); if (canceled) return; for (const badge of frequentlyChangingBadges) { - subscriptions.add( - badge.subscribeToCompatibilityChanges(apiContext, (isCompatible, action) => - handleActionCompatibilityChange('badge', isCompatible, action as AnyApiAction) + const compatibilitySubject = badge + .getCompatibilityChangesSubject(apiContext) + ?.pipe( + switchMap(async () => { + return await badge.isCompatible({ + ...apiContext, + trigger: panelBadgeTrigger, + }); + }) ) - ); + .subscribe(async (isCompatible) => { + handleActionCompatibilityChange('badge', isCompatible, badge as AnyApiAction); + }); + subscriptions.add(compatibilitySubject); } // subscribe to any frequently changing notification actions @@ -101,12 +110,26 @@ export const usePresentationPanelHeaderActions = < ); if (canceled) return; for (const notification of frequentlyChangingNotifications) { - if (!disabledNotifications.includes(notification.id)) - subscriptions.add( - notification.subscribeToCompatibilityChanges(apiContext, (isCompatible, action) => - handleActionCompatibilityChange('notification', isCompatible, action as AnyApiAction) + if (!disabledNotifications.includes(notification.id)) { + const compatibilitySubject = notification + .getCompatibilityChangesSubject(apiContext) + ?.pipe( + switchMap(async () => { + return await notification.isCompatible({ + ...apiContext, + trigger: panelNotificationTrigger, + }); + }) ) - ); + .subscribe(async (isCompatible) => { + handleActionCompatibilityChange( + 'notification', + isCompatible, + notification as AnyApiAction + ); + }); + subscriptions.add(compatibilitySubject); + } } })(); diff --git a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_error_internal.tsx b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_error_internal.tsx index 3a5c7c5b7830d..7948efac46114 100644 --- a/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_error_internal.tsx +++ b/src/platform/plugins/private/presentation_panel/public/panel_component/presentation_panel_error_internal.tsx @@ -14,7 +14,7 @@ import { ErrorLike } from '@kbn/expressions-plugin/common'; import { EmbeddableApiContext, useStateFromPublishingSubject } from '@kbn/presentation-publishing'; import { renderSearchError } from '@kbn/search-errors'; import { Markdown } from '@kbn/shared-ux-markdown'; -import { Subscription } from 'rxjs'; +import { Subscription, switchMap } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { useErrorTextStyle } from '@kbn/react-hooks'; import { ActionExecutionMeta } from '@kbn/ui-actions-plugin/public'; @@ -80,20 +80,26 @@ export const PresentationPanelErrorInternal = ({ api, error }: PresentationPanel const subscription = new Subscription(); (async () => { const editPanelAction = await uiActions.getAction(ACTION_EDIT_PANEL); - if (canceled || !editPanelAction?.couldBecomeCompatible?.({ embeddable: api })) return; - - const initiallyCompatible = await editPanelAction?.isCompatible({ + const context = { embeddable: api, trigger: { id: CONTEXT_MENU_TRIGGER }, - } as EmbeddableApiContext & ActionExecutionMeta); + }; + if (canceled || !editPanelAction?.couldBecomeCompatible?.(context)) return; + + const initiallyCompatible = await editPanelAction?.isCompatible(context); if (canceled) return; setIsEditable(initiallyCompatible); - - subscription.add( - editPanelAction?.subscribeToCompatibilityChanges?.({ embeddable: api }, (isCompatible) => { + const compatibilitySubscription = editPanelAction + ?.getCompatibilityChangesSubject?.(context) + ?.pipe( + switchMap(async () => { + return await editPanelAction.isCompatible(context); + }) + ) + .subscribe(async (isCompatible) => { if (!canceled) setIsEditable(isCompatible); - }) - ); + }); + subscription.add(compatibilitySubscription); })(); return () => { diff --git a/src/platform/plugins/shared/controls/public/actions/clear_control_action.test.tsx b/src/platform/plugins/shared/controls/public/actions/clear_control_action.test.tsx index 19b182ecb6d62..4059e83a2a754 100644 --- a/src/platform/plugins/shared/controls/public/actions/clear_control_action.test.tsx +++ b/src/platform/plugins/shared/controls/public/actions/clear_control_action.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { getMockedControlGroupApi } from '../controls/mocks/control_mocks'; import { ClearControlAction } from './clear_control_action'; @@ -46,13 +46,12 @@ describe('ClearControlAction', () => { }).rejects.toThrow(Error); }); - test('should call onChange when isCompatible changes', () => { - const onChange = jest.fn(); - + test('should call onChange when isCompatible changes', (done) => { + const subject = clearControlAction.getCompatibilityChangesSubject({ embeddable: controlApi }); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); hasSelections$.next(true); - clearControlAction.subscribeToCompatibilityChanges({ embeddable: controlApi }, onChange); - - expect(onChange).toHaveBeenCalledWith(true, clearControlAction); }); describe('Clear control button compatibility', () => { diff --git a/src/platform/plugins/shared/controls/public/actions/clear_control_action.tsx b/src/platform/plugins/shared/controls/public/actions/clear_control_action.tsx index f435f7e885c5b..6440d7093d5a0 100644 --- a/src/platform/plugins/shared/controls/public/actions/clear_control_action.tsx +++ b/src/platform/plugins/shared/controls/public/actions/clear_control_action.tsx @@ -28,6 +28,7 @@ import { type Action, } from '@kbn/ui-actions-plugin/public'; import { PresentationContainer, apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { map } from 'rxjs'; import { CONTROL_GROUP_TYPE } from '../../common'; import { CanClearSelections, isClearableControl } from '../types'; @@ -89,15 +90,10 @@ export class ClearControlAction return isClearableControl(embeddable); } - public subscribeToCompatibilityChanges( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: ClearControlAction) => void - ) { - if (!isClearableControl(embeddable)) return; - - return embeddable.hasSelections$.subscribe((selection) => { - onChange(Boolean(selection), this); - }); + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { + return isClearableControl(embeddable) + ? embeddable.hasSelections$.pipe(map(() => undefined)) + : undefined; } public async isCompatible({ embeddable }: EmbeddableApiContext) { diff --git a/src/platform/plugins/shared/controls/public/control_group/components/floating_actions.tsx b/src/platform/plugins/shared/controls/public/control_group/components/floating_actions.tsx index b6c51619eda0e..b17bc8e0bc056 100644 --- a/src/platform/plugins/shared/controls/public/control_group/components/floating_actions.tsx +++ b/src/platform/plugins/shared/controls/public/control_group/components/floating_actions.tsx @@ -10,7 +10,7 @@ import classNames from 'classnames'; import React, { FC, ReactElement, useEffect, useState } from 'react'; import { v4 } from 'uuid'; -import { Subscription } from 'rxjs'; +import { Subscription, switchMap } from 'rxjs'; import { type ViewMode } from '@kbn/embeddable-plugin/public'; import { apiHasUniqueId } from '@kbn/presentation-publishing'; @@ -96,9 +96,17 @@ export const FloatingActions: FC = ({ if (canceled) return; for (const action of frequentlyChangingActions) { - subscriptions.add( - action.subscribeToCompatibilityChanges(context, handleActionCompatibilityChange) - ); + const compatibilitySubscription = action + .getCompatibilityChangesSubject(context) + ?.pipe( + switchMap(async () => { + return await action.isCompatible(context); + }) + ) + .subscribe(async (isCompatible) => { + handleActionCompatibilityChange(isCompatible, action); + }); + subscriptions.add(compatibilitySubscription); } })(); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.test.tsx index a2806a641db6a..36b0efa22fa6e 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.test.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { ExpandPanelActionApi, ExpandPanelAction } from './expand_panel_action'; describe('Expand panel action', () => { @@ -40,11 +40,12 @@ describe('Expand panel action', () => { expect(await action.isCompatible(emptyContext)).toBe(false); }); - it('calls onChange when expandedPanelId changes', async () => { - const onChange = jest.fn(); - action.subscribeToCompatibilityChanges(context, onChange); + it('getCompatibilityChangesSubject emits when expandedPanelId changes', (done) => { + const subject = action.getCompatibilityChangesSubject(context); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); expandedPanelId$.next('superPanelId'); - expect(onChange).toHaveBeenCalledWith(true, action); }); it('returns the correct icon based on expanded panel id', async () => { diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.tsx index a1d915deec22b..e42d3c7478f85 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/expand_panel_action.tsx @@ -16,7 +16,7 @@ import { HasUniqueId, } from '@kbn/presentation-publishing'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; -import { skip } from 'rxjs'; +import { map, skip } from 'rxjs'; import { dashboardExpandPanelActionStrings } from './_dashboard_actions_strings'; import { ACTION_EXPAND_PANEL, DASHBOARD_ACTION_GROUP } from './constants'; @@ -52,14 +52,13 @@ export class ExpandPanelAction implements Action { return apiHasParentApi(embeddable) && apiCanExpandPanels(embeddable.parentApi); } - public subscribeToCompatibilityChanges( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: ExpandPanelAction) => void - ) { - if (!isApiCompatible(embeddable)) return; - return embeddable.parentApi.expandedPanelId$.pipe(skip(1)).subscribe(() => { - onChange(isApiCompatible(embeddable), this); - }); + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { + return isApiCompatible(embeddable) + ? embeddable.parentApi.expandedPanelId$.pipe( + skip(1), + map(() => undefined) + ) + : undefined; } public async execute({ embeddable }: EmbeddableApiContext) { diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.test.tsx index e639168b00c7f..e7b5eb4874dc7 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.test.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.test.tsx @@ -9,7 +9,7 @@ import { Filter, FilterStateStore, type AggregateQuery, type Query } from '@kbn/es-query'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { FiltersNotificationAction, FiltersNotificationActionApi, @@ -77,17 +77,19 @@ describe('filters notification action', () => { expect(await action.isCompatible(context)).toBe(true); }); - it('calls onChange when filters change', async () => { - const onChange = jest.fn(); - action.subscribeToCompatibilityChanges(context, onChange); + it('getCompatibilityChangesSubject emits when filters change', (done) => { + const subject = action.getCompatibilityChangesSubject(context); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); updateFilters([getMockPhraseFilter('SuperField', 'SuperValue')]); - expect(onChange).toHaveBeenCalledWith(true, action); }); - it('calls onChange when query changes', async () => { - const onChange = jest.fn(); - action.subscribeToCompatibilityChanges(context, onChange); + it('getCompatibilityChangesSubject emits when query changes', (done) => { + const subject = action.getCompatibilityChangesSubject(context); + subject?.pipe(take(1)).subscribe(() => { + done(); + }); updateQuery({ esql: 'FROM test_dataview' } as AggregateQuery); - expect(onChange).toHaveBeenCalledWith(true, action); }); }); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.tsx index fe19b68ff7a08..cfac51e5489c8 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/filters_notification_action.tsx @@ -8,7 +8,7 @@ */ import React from 'react'; -import { merge } from 'rxjs'; +import { map, merge } from 'rxjs'; import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; @@ -87,14 +87,11 @@ export class FiltersNotificationAction implements Action { return apiPublishesPartialUnifiedSearch(embeddable); } - public subscribeToCompatibilityChanges( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: FiltersNotificationAction) => void - ) { + public getCompatibilityChangesSubject({ embeddable }: EmbeddableApiContext) { if (!isApiCompatible(embeddable)) return; return merge( ...[embeddable.query$, embeddable.filters$].filter((value) => Boolean(value)) - ).subscribe(() => onChange(compatibilityCheck(embeddable), this)); + ).pipe(map(() => undefined)); } public execute = async () => {}; diff --git a/src/platform/plugins/shared/ui_actions/public/actions/action.ts b/src/platform/plugins/shared/ui_actions/public/actions/action.ts index a4e2b1e8e8548..ebde9b5dd22b0 100644 --- a/src/platform/plugins/shared/ui_actions/public/actions/action.ts +++ b/src/platform/plugins/shared/ui_actions/public/actions/action.ts @@ -9,7 +9,7 @@ import type { Presentable } from '@kbn/ui-actions-browser/src/types'; import type { Trigger } from '@kbn/ui-actions-browser/src/triggers'; -import { Subscription } from 'rxjs'; +import { Observable } from 'rxjs'; /** * During action execution we can provide additional information, @@ -41,7 +41,7 @@ export interface ActionMenuItemProps { } export type FrequentCompatibilityChangeAction = Action & - Required, 'subscribeToCompatibilityChanges' | 'couldBecomeCompatible'>>; + Required, 'getCompatibilityChangesSubject' | 'couldBecomeCompatible'>>; export interface Action extends Partial>> { @@ -98,13 +98,9 @@ export interface Action shouldAutoExecute?(context: ActionExecutionContext): Promise; /** - * Allows this action to call a method when its compatibility changes. - * @returns a subscription that can be used to unsubscribe from the changes. + * @returns an Observable that emits when this action's compatibility changes. */ - subscribeToCompatibilityChanges?: ( - context: Context, - onChange: (isCompatible: boolean, action: Action) => void - ) => Subscription | undefined; + getCompatibilityChangesSubject?: (context: Context) => Observable | undefined; /** * Determines if action could become compatible given the context. If present, @@ -179,13 +175,9 @@ export interface ActionDefinition showNotification?: boolean; /** - * Allows this action to call a method when its compatibility changes. - * @returns a subscription that can be used to unsubscribe from the changes. + * @returns an Observable that emits when this action's compatibility should be recalculated. */ - subscribeToCompatibilityChanges?: ( - context: Context, - onChange: (isCompatible: boolean, action: Action) => void - ) => Subscription | undefined; + getCompatibilityChangesSubject?: (context: Context) => Observable | undefined; /** * Determines if action could become compatible given the context. If present, diff --git a/src/platform/plugins/shared/ui_actions/public/actions/action_internal.ts b/src/platform/plugins/shared/ui_actions/public/actions/action_internal.ts index 6f979849bdc41..313ac79e1b80e 100644 --- a/src/platform/plugins/shared/ui_actions/public/actions/action_internal.ts +++ b/src/platform/plugins/shared/ui_actions/public/actions/action_internal.ts @@ -27,7 +27,7 @@ export class ActionInternal public readonly showNotification?: boolean; public readonly disabled?: boolean; - public readonly subscribeToCompatibilityChanges?: Action['subscribeToCompatibilityChanges']; + public readonly getCompatibilityChangesSubject?: Action['getCompatibilityChangesSubject']; public readonly couldBecomeCompatible?: Action['couldBecomeCompatible']; public errorLogged?: boolean; @@ -41,8 +41,8 @@ export class ActionInternal this.disabled = this.definition.disabled; this.errorLogged = false; - if (this.definition.subscribeToCompatibilityChanges) { - this.subscribeToCompatibilityChanges = definition.subscribeToCompatibilityChanges; + if (this.definition.getCompatibilityChangesSubject) { + this.getCompatibilityChangesSubject = definition.getCompatibilityChangesSubject; } if (this.definition.couldBecomeCompatible) { this.couldBecomeCompatible = definition.couldBecomeCompatible; diff --git a/src/platform/plugins/shared/ui_actions/public/service/ui_actions_service.ts b/src/platform/plugins/shared/ui_actions/public/service/ui_actions_service.ts index 581be20979cb5..a5c51765c2b3a 100644 --- a/src/platform/plugins/shared/ui_actions/public/service/ui_actions_service.ts +++ b/src/platform/plugins/shared/ui_actions/public/service/ui_actions_service.ts @@ -222,7 +222,7 @@ export class UiActionsService { ): Promise => { return (await this.getTriggerActions(triggerId)).filter((action) => { return ( - Boolean(action.subscribeToCompatibilityChanges) && + Boolean(action.getCompatibilityChangesSubject) && action.couldBecomeCompatible?.({ ...context, trigger: this.getTrigger(triggerId), diff --git a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_action.ts index fa67aa74f9de3..15ee562711320 100644 --- a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_action.ts @@ -6,9 +6,10 @@ */ import { i18n } from '@kbn/i18n'; -import { Action, createAction, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { createAction, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { EmbeddableApiContext } from '@kbn/presentation-publishing'; import type { DataViewsService } from '@kbn/data-views-plugin/public'; +import { map } from 'rxjs'; import type { DiscoverAppLocator } from './open_in_discover_helpers'; import { LensApi } from '../react_embeddable/types'; @@ -53,15 +54,9 @@ export const createOpenInDiscoverAction = ( throw new IncompatibleActionError(); return hasDiscoverAccess && Boolean((embeddable as LensApi).canViewUnderlyingData$); }, - subscribeToCompatibilityChanges: ( - { embeddable }: EmbeddableApiContext, - onChange: (isCompatible: boolean, action: Action) => void - ) => { - if (!typeof (embeddable as LensApi).canViewUnderlyingData$) - throw new IncompatibleActionError(); - return (embeddable as LensApi).canViewUnderlyingData$.subscribe((canViewUnderlyingData) => { - onChange(canViewUnderlyingData, actionDefinition); - }); + getCompatibilityChangesSubject: ({ embeddable }: EmbeddableApiContext) => { + if (!typeof (embeddable as LensApi).canViewUnderlyingData$) return; + return (embeddable as LensApi).canViewUnderlyingData$.pipe(map(() => undefined)); }, execute: async (context: EmbeddableApiContext) => { const { execute } = await getDiscoverHelpersAsync();