mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[uiActions] make trigger action registry async (#205512)
Closes https://github.com/elastic/kibana/issues/191642, part of https://github.com/elastic/kibana/pull/205512 PR makes the following changes to uiActions API. * Deprecates `registerAction` and `addTriggerAction` and replaces them with `registerActionAsync` and `addTriggerActionAsync` * Makes all registry consumption methods async, such as `getAction`, `getTriggerActions` and `getFrequentlyChangingActionsForTrigger` PR updates presentation_panel plugin to use `registerActionAsync`. With actions behind async import, page load bundle size has been reduced by 21.1KB. <img width="500" alt="Screenshot 2025-01-08 at 2 14 23 PM" src="https://github.com/user-attachments/assets/34a2cae9-dc5e-429b-bbdb-ffd9dfe1cce3" /> Also, async exports are [contained in a single file `panel_module`](https://github.com/elastic/kibana/issues/206117). This results in dashboard only loading one `presentationPanel.chunk`. <img width="500" alt="Screenshot 2025-01-08 at 2 15 02 PM" src="https://github.com/user-attachments/assets/e083b852-b50d-4fa7-8ebd-e2f56f85e998" /> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
fe0bc34069
commit
1bd8c97982
53 changed files with 528 additions and 321 deletions
|
@ -24,36 +24,24 @@ export function AddButton({ pageApi, uiActions }: { pageApi: unknown; uiActions:
|
|||
id: ADD_PANEL_TRIGGER,
|
||||
},
|
||||
};
|
||||
const actionsPromises = uiActions.getTriggerActions(ADD_PANEL_TRIGGER).map(async (action) => {
|
||||
return {
|
||||
isCompatible: await action.isCompatible(actionContext),
|
||||
action,
|
||||
};
|
||||
});
|
||||
|
||||
Promise.all(actionsPromises).then((actions) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
uiActions.getTriggerCompatibleActions(ADD_PANEL_TRIGGER, actionContext).then((actions) => {
|
||||
if (cancelled) return;
|
||||
|
||||
const nextItems = actions
|
||||
.filter(
|
||||
({ action, isCompatible }) => isCompatible && action.id !== 'ACTION_CREATE_ESQL_CHART'
|
||||
)
|
||||
.map(({ action }) => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key={action.id}
|
||||
icon="share"
|
||||
onClick={() => {
|
||||
action.execute(actionContext);
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
{action.getDisplayName(actionContext)}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
});
|
||||
const nextItems = actions.map((action) => {
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key={action.id}
|
||||
icon="share"
|
||||
onClick={() => {
|
||||
action.execute(actionContext);
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
{action.getDisplayName(actionContext)}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
});
|
||||
setItems(nextItems);
|
||||
});
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ export function plugin() {
|
|||
return new PresentationPanelPlugin();
|
||||
}
|
||||
|
||||
export { getEditPanelAction, ACTION_CUSTOMIZE_PANEL } from './panel_actions';
|
||||
export { ACTION_CUSTOMIZE_PANEL } from './panel_actions/customize_panel_action/constants';
|
||||
export { ACTION_EDIT_PANEL } from './panel_actions/edit_panel_action/constants';
|
||||
export { PresentationPanel } from './panel_component';
|
||||
export type { PresentationPanelProps } from './panel_component/types';
|
||||
export { PresentationPanelError } from './panel_component/presentation_panel_error';
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { PresentationPanelStartDependencies } from './plugin';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { PresentationPanelStartDependencies } from './plugin';
|
||||
|
||||
export let core: CoreStart;
|
||||
export let uiActions: PresentationPanelStartDependencies['uiActions'];
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const ACTION_CUSTOMIZE_PANEL = 'ACTION_CUSTOMIZE_PANEL';
|
||||
export const CUSTOM_TIME_RANGE_BADGE = 'CUSTOM_TIME_RANGE_BADGE';
|
|
@ -10,6 +10,7 @@
|
|||
import { PrettyDuration } from '@elastic/eui';
|
||||
import {
|
||||
Action,
|
||||
ActionExecutionMeta,
|
||||
FrequentCompatibilityChangeAction,
|
||||
IncompatibleActionError,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -17,10 +18,8 @@ import React from 'react';
|
|||
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { apiPublishesTimeRange, EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { core } from '../../kibana_services';
|
||||
import { customizePanelAction } from '../panel_actions';
|
||||
|
||||
export const CUSTOM_TIME_RANGE_BADGE = 'CUSTOM_TIME_RANGE_BADGE';
|
||||
import { ACTION_CUSTOMIZE_PANEL, CUSTOM_TIME_RANGE_BADGE } from './constants';
|
||||
import { core, uiActions } from '../../kibana_services';
|
||||
|
||||
export class CustomTimeRangeBadge
|
||||
implements Action<EmbeddableApiContext>, FrequentCompatibilityChangeAction<EmbeddableApiContext>
|
||||
|
@ -69,8 +68,9 @@ export class CustomTimeRangeBadge
|
|||
});
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
customizePanelAction.execute({ embeddable });
|
||||
public async execute(context: ActionExecutionMeta & EmbeddableApiContext) {
|
||||
const action = await uiActions.getAction(ACTION_CUSTOMIZE_PANEL);
|
||||
action.execute(context);
|
||||
}
|
||||
|
||||
public getIconType() {
|
||||
|
|
|
@ -26,8 +26,7 @@ import {
|
|||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { openCustomizePanelFlyout } from './open_customize_panel';
|
||||
|
||||
export const ACTION_CUSTOMIZE_PANEL = 'ACTION_CUSTOMIZE_PANEL';
|
||||
import { ACTION_CUSTOMIZE_PANEL } from './constants';
|
||||
|
||||
export type CustomizePanelActionApi = CanAccessViewMode &
|
||||
Partial<
|
||||
|
|
|
@ -16,8 +16,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { hasEditCapabilities } from '@kbn/presentation-publishing';
|
||||
import { FilterItems } from '@kbn/unified-search-plugin/public';
|
||||
import { editPanelAction } from '../panel_actions';
|
||||
import { CustomizePanelActionApi } from './customize_panel_action';
|
||||
import { executeEditPanelAction } from '../edit_panel_action/execute_edit_action';
|
||||
|
||||
export const filterDetailsActionStrings = {
|
||||
getQueryTitle: () =>
|
||||
|
@ -75,7 +75,7 @@ export function FiltersDetails({ editMode, api }: FiltersDetailsProps) {
|
|||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="customizePanelEditQueryButton"
|
||||
onClick={() => editPanelAction.execute({ embeddable: api })}
|
||||
onClick={() => executeEditPanelAction(api)}
|
||||
aria-label={i18n.translate(
|
||||
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.editQueryButtonAriaLabel',
|
||||
{
|
||||
|
@ -112,7 +112,7 @@ export function FiltersDetails({ editMode, api }: FiltersDetailsProps) {
|
|||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="customizePanelEditFiltersButton"
|
||||
onClick={() => editPanelAction.execute({ embeddable: api })}
|
||||
onClick={() => executeEditPanelAction(api)}
|
||||
aria-label={i18n.translate(
|
||||
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.editFiltersButtonAriaLabel',
|
||||
{
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const ACTION_EDIT_PANEL = 'editPanel';
|
|
@ -23,8 +23,7 @@ import {
|
|||
FrequentCompatibilityChangeAction,
|
||||
IncompatibleActionError,
|
||||
} from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
export const ACTION_EDIT_PANEL = 'editPanel';
|
||||
import { ACTION_EDIT_PANEL } from './constants';
|
||||
|
||||
export type EditPanelActionApi = CanAccessViewMode & HasEditCapabilities;
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { ActionExecutionMeta } from '@kbn/ui-actions-plugin/public';
|
||||
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { CONTEXT_MENU_TRIGGER } from '../triggers';
|
||||
import { ACTION_EDIT_PANEL } from './constants';
|
||||
import { uiActions } from '../../kibana_services';
|
||||
|
||||
export async function executeEditPanelAction(api: unknown) {
|
||||
try {
|
||||
const action = await uiActions.getAction(ACTION_EDIT_PANEL);
|
||||
action.execute({
|
||||
embeddable: api,
|
||||
trigger: { id: CONTEXT_MENU_TRIGGER },
|
||||
} as EmbeddableApiContext & ActionExecutionMeta);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unable to execute edit action, Error: ', error.message);
|
||||
}
|
||||
}
|
|
@ -7,15 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export {
|
||||
ACTION_CUSTOMIZE_PANEL,
|
||||
CustomizePanelAction,
|
||||
CustomTimeRangeBadge,
|
||||
} from './customize_panel_action';
|
||||
export { EditPanelAction } from './edit_panel_action/edit_panel_action';
|
||||
export { InspectPanelAction } from './inspect_panel_action/inspect_panel_action';
|
||||
export { getEditPanelAction } from './panel_actions';
|
||||
export { RemovePanelAction } from './remove_panel_action/remove_panel_action';
|
||||
export {
|
||||
contextMenuTrigger,
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const ACTION_INSPECT_PANEL = 'openInspector';
|
|
@ -17,10 +17,9 @@ import {
|
|||
HasParentApi,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { ACTION_INSPECT_PANEL } from './constants';
|
||||
import { inspector } from '../../kibana_services';
|
||||
|
||||
export const ACTION_INSPECT_PANEL = 'openInspector';
|
||||
|
||||
export type InspectPanelActionApi = HasInspectorAdapters &
|
||||
Partial<PublishesPanelTitle & HasParentApi>;
|
||||
const isApiCompatible = (api: unknown | null): api is InspectPanelActionApi => {
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { uiActions } from '../kibana_services';
|
||||
import { CustomizePanelAction, CustomTimeRangeBadge } from './customize_panel_action';
|
||||
import { EditPanelAction } from './edit_panel_action/edit_panel_action';
|
||||
import { InspectPanelAction } from './inspect_panel_action/inspect_panel_action';
|
||||
import { RemovePanelAction } from './remove_panel_action/remove_panel_action';
|
||||
import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from './triggers';
|
||||
|
||||
// export these actions to make them accessible in this plugin.
|
||||
export let customizePanelAction: CustomizePanelAction;
|
||||
export let editPanelAction: EditPanelAction;
|
||||
|
||||
export const getEditPanelAction = () => editPanelAction;
|
||||
|
||||
export const registerActions = () => {
|
||||
editPanelAction = new EditPanelAction();
|
||||
customizePanelAction = new CustomizePanelAction();
|
||||
|
||||
const removePanel = new RemovePanelAction();
|
||||
const inspectPanel = new InspectPanelAction();
|
||||
const timeRangeBadge = new CustomTimeRangeBadge();
|
||||
|
||||
uiActions.registerAction(removePanel);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, removePanel.id);
|
||||
|
||||
uiActions.registerAction(timeRangeBadge);
|
||||
uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge.id);
|
||||
|
||||
uiActions.registerAction(inspectPanel);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, inspectPanel.id);
|
||||
|
||||
uiActions.registerAction(editPanelAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, editPanelAction.id);
|
||||
|
||||
uiActions.registerAction(customizePanelAction);
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, customizePanelAction.id);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { uiActions } from '../kibana_services';
|
||||
import { ACTION_EDIT_PANEL } from './edit_panel_action/constants';
|
||||
import { ACTION_INSPECT_PANEL } from './inspect_panel_action/constants';
|
||||
import { ACTION_REMOVE_PANEL } from './remove_panel_action/constants';
|
||||
import {
|
||||
ACTION_CUSTOMIZE_PANEL,
|
||||
CUSTOM_TIME_RANGE_BADGE,
|
||||
} from './customize_panel_action/constants';
|
||||
import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from './triggers';
|
||||
|
||||
export const registerActions = () => {
|
||||
uiActions.registerActionAsync(ACTION_REMOVE_PANEL, async () => {
|
||||
const { RemovePanelAction } = await import('../panel_component/panel_module');
|
||||
return new RemovePanelAction();
|
||||
});
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_REMOVE_PANEL);
|
||||
|
||||
uiActions.registerActionAsync(CUSTOM_TIME_RANGE_BADGE, async () => {
|
||||
const { CustomTimeRangeBadge } = await import('../panel_component/panel_module');
|
||||
return new CustomTimeRangeBadge();
|
||||
});
|
||||
uiActions.attachAction(PANEL_BADGE_TRIGGER, CUSTOM_TIME_RANGE_BADGE);
|
||||
|
||||
uiActions.registerActionAsync(ACTION_INSPECT_PANEL, async () => {
|
||||
const { InspectPanelAction } = await import('../panel_component/panel_module');
|
||||
return new InspectPanelAction();
|
||||
});
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_INSPECT_PANEL);
|
||||
|
||||
uiActions.registerActionAsync(ACTION_EDIT_PANEL, async () => {
|
||||
const { EditPanelAction } = await import('../panel_component/panel_module');
|
||||
return new EditPanelAction();
|
||||
});
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_EDIT_PANEL);
|
||||
|
||||
uiActions.registerActionAsync(ACTION_CUSTOMIZE_PANEL, async () => {
|
||||
const { CustomizePanelAction } = await import('../panel_component/panel_module');
|
||||
return new CustomizePanelAction();
|
||||
});
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, ACTION_CUSTOMIZE_PANEL);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const ACTION_REMOVE_PANEL = 'deletePanel';
|
|
@ -18,10 +18,8 @@ import {
|
|||
PublishesViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { getContainerParentFromAPI, PresentationContainer } from '@kbn/presentation-containers';
|
||||
|
||||
export const ACTION_REMOVE_PANEL = 'deletePanel';
|
||||
import { ACTION_REMOVE_PANEL } from './constants';
|
||||
|
||||
export type RemovePanelActionApi = PublishesViewMode &
|
||||
HasUniqueId &
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
|
||||
import { Action } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
export type AnyApiAction = Action<EmbeddableApiContext>;
|
||||
|
|
|
@ -243,10 +243,11 @@ export const PresentationPanelHoverActions = ({
|
|||
|
||||
(async () => {
|
||||
// subscribe to any frequently changing context menu actions
|
||||
const frequentlyChangingActions = uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
const frequentlyChangingActions = await uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
if (canceled) return;
|
||||
|
||||
for (const frequentlyChangingAction of frequentlyChangingActions) {
|
||||
if ((quickActionIds as readonly string[]).includes(frequentlyChangingAction.id)) {
|
||||
|
@ -265,10 +266,12 @@ export const PresentationPanelHoverActions = ({
|
|||
}
|
||||
|
||||
// subscribe to any frequently changing notification actions
|
||||
const frequentlyChangingNotifications = uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_NOTIFICATION_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
const frequentlyChangingNotifications =
|
||||
await uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_NOTIFICATION_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
if (canceled) return;
|
||||
|
||||
for (const frequentlyChangingNotification of frequentlyChangingNotifications) {
|
||||
if (
|
||||
|
|
|
@ -80,10 +80,11 @@ export const usePresentationPanelHeaderActions = <
|
|||
const apiContext = { embeddable: api };
|
||||
|
||||
// subscribe to any frequently changing badge actions
|
||||
const frequentlyChangingBadges = uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
const frequentlyChangingBadges = await uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_BADGE_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
if (canceled) return;
|
||||
for (const badge of frequentlyChangingBadges) {
|
||||
subscriptions.add(
|
||||
badge.subscribeToCompatibilityChanges(apiContext, (isCompatible, action) =>
|
||||
|
@ -93,10 +94,12 @@ export const usePresentationPanelHeaderActions = <
|
|||
}
|
||||
|
||||
// subscribe to any frequently changing notification actions
|
||||
const frequentlyChangingNotifications = uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_NOTIFICATION_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
const frequentlyChangingNotifications =
|
||||
await uiActions.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_NOTIFICATION_TRIGGER,
|
||||
apiContext
|
||||
);
|
||||
if (canceled) return;
|
||||
for (const notification of frequentlyChangingNotifications) {
|
||||
if (!disabledNotifications.includes(notification.id))
|
||||
subscriptions.add(
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { PresentationPanelInternal } from './presentation_panel_internal';
|
||||
export { PresentationPanelErrorInternal } from './presentation_panel_error_internal';
|
||||
export { RemovePanelAction } from '../panel_actions/remove_panel_action/remove_panel_action';
|
||||
export { CustomTimeRangeBadge } from '../panel_actions/customize_panel_action';
|
||||
export { CustomizePanelAction } from '../panel_actions/customize_panel_action';
|
||||
export { EditPanelAction } from '../panel_actions/edit_panel_action/edit_panel_action';
|
||||
export { InspectPanelAction } from '../panel_actions/inspect_panel_action/inspect_panel_action';
|
|
@ -16,8 +16,7 @@ import React from 'react';
|
|||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { css } from '@emotion/react';
|
||||
import { untilPluginStartServicesReady } from '../kibana_services';
|
||||
import { PresentationPanelError } from './presentation_panel_error';
|
||||
import { DefaultPresentationPanelApi, PresentationPanelProps } from './types';
|
||||
import type { DefaultPresentationPanelApi, PresentationPanelProps } from './types';
|
||||
import { getErrorLoadingPanel } from './presentation_panel_strings';
|
||||
|
||||
export const PresentationPanel = <
|
||||
|
@ -30,7 +29,7 @@ export const PresentationPanel = <
|
|||
) => {
|
||||
const { Component, hidePanelChrome, ...passThroughProps } = props;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { loading, value, error } = useAsync(async () => {
|
||||
const { loading, value } = useAsync(async () => {
|
||||
if (hidePanelChrome) {
|
||||
return {
|
||||
unwrappedComponent: isPromise(Component) ? await Component : Component,
|
||||
|
@ -38,15 +37,31 @@ export const PresentationPanel = <
|
|||
}
|
||||
|
||||
const startServicesPromise = untilPluginStartServicesReady();
|
||||
const modulePromise = await import('./presentation_panel_internal');
|
||||
const componentPromise = isPromise(Component) ? Component : Promise.resolve(Component);
|
||||
const [, unwrappedComponent, panelModule] = await Promise.all([
|
||||
const results = await Promise.allSettled([
|
||||
startServicesPromise,
|
||||
componentPromise,
|
||||
modulePromise,
|
||||
import('./panel_module'),
|
||||
]);
|
||||
const Panel = panelModule.PresentationPanelInternal;
|
||||
return { Panel, unwrappedComponent };
|
||||
|
||||
let loadErrorReason: string | undefined;
|
||||
for (const result of results) {
|
||||
if (result.status === 'rejected') {
|
||||
loadErrorReason = result.reason;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loadErrorReason,
|
||||
Panel:
|
||||
results[2].status === 'fulfilled' ? results[2].value?.PresentationPanelInternal : undefined,
|
||||
PanelError:
|
||||
results[2].status === 'fulfilled'
|
||||
? results[2].value?.PresentationPanelErrorInternal
|
||||
: undefined,
|
||||
unwrappedComponent: results[1].status === 'fulfilled' ? results[1].value : undefined,
|
||||
};
|
||||
|
||||
// Ancestry chain is expected to use 'key' attribute to reset DOM and state
|
||||
// when unwrappedComponent needs to be re-loaded
|
||||
|
@ -66,9 +81,10 @@ export const PresentationPanel = <
|
|||
);
|
||||
|
||||
const Panel = value?.Panel;
|
||||
const PanelError = value?.PanelError;
|
||||
const UnwrappedComponent = value?.unwrappedComponent;
|
||||
const shouldHavePanel = !hidePanelChrome;
|
||||
if (error || (shouldHavePanel && !Panel) || !UnwrappedComponent) {
|
||||
if (value?.loadErrorReason || (shouldHavePanel && !Panel) || !UnwrappedComponent) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -76,7 +92,11 @@ export const PresentationPanel = <
|
|||
data-test-subj="embeddableError"
|
||||
justifyContent="center"
|
||||
>
|
||||
<PresentationPanelError error={error ?? new Error(getErrorLoadingPanel())} />
|
||||
{PanelError ? (
|
||||
<PanelError error={new Error(value?.loadErrorReason ?? getErrorLoadingPanel())} />
|
||||
) : (
|
||||
value?.loadErrorReason ?? getErrorLoadingPanel()
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,100 +7,21 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiEmptyPrompt, EuiText } from '@elastic/eui';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { dynamic } from '@kbn/shared-ux-utility';
|
||||
import { PanelLoader } from '@kbn/panel-loader';
|
||||
import type { PresentationPanelErrorProps } from './presentation_panel_error_internal';
|
||||
|
||||
import { ErrorLike } from '@kbn/expressions-plugin/common';
|
||||
import { useStateFromPublishingSubject } from '@kbn/presentation-publishing';
|
||||
import { renderSearchError } from '@kbn/search-errors';
|
||||
import { Markdown } from '@kbn/shared-ux-markdown';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useErrorTextStyle } from '@kbn/react-hooks';
|
||||
import { editPanelAction } from '../panel_actions/panel_actions';
|
||||
import { getErrorCallToAction } from './presentation_panel_strings';
|
||||
import { DefaultPresentationPanelApi } from './types';
|
||||
|
||||
export const PresentationPanelError = ({
|
||||
api,
|
||||
error,
|
||||
}: {
|
||||
error: ErrorLike;
|
||||
api?: DefaultPresentationPanelApi;
|
||||
}) => {
|
||||
const errorTextStyle = useErrorTextStyle();
|
||||
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const handleErrorClick = useMemo(
|
||||
() => (isEditable ? () => editPanelAction?.execute({ embeddable: api }) : undefined),
|
||||
[api, isEditable]
|
||||
);
|
||||
const label = useMemo(
|
||||
() => (isEditable ? editPanelAction?.getDisplayName({ embeddable: api }) : ''),
|
||||
[api, isEditable]
|
||||
);
|
||||
|
||||
const panelTitle = useStateFromPublishingSubject(api?.panelTitle);
|
||||
const ariaLabel = useMemo(
|
||||
() => (panelTitle ? getErrorCallToAction(panelTitle) : label),
|
||||
[label, panelTitle]
|
||||
);
|
||||
|
||||
// Get initial editable state from action and subscribe to changes.
|
||||
useEffect(() => {
|
||||
if (!editPanelAction?.couldBecomeCompatible({ embeddable: api })) return;
|
||||
|
||||
let canceled = false;
|
||||
const subscription = new Subscription();
|
||||
(async () => {
|
||||
const initiallyCompatible = await editPanelAction?.isCompatible({ embeddable: api });
|
||||
if (canceled) return;
|
||||
setIsEditable(initiallyCompatible);
|
||||
|
||||
subscription.add(
|
||||
editPanelAction?.subscribeToCompatibilityChanges({ embeddable: api }, (isCompatible) => {
|
||||
if (!canceled) setIsEditable(isCompatible);
|
||||
})
|
||||
);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
subscription.unsubscribe();
|
||||
const Component = dynamic(
|
||||
async () => {
|
||||
const { PresentationPanelErrorInternal } = await import('./panel_module');
|
||||
return {
|
||||
default: PresentationPanelErrorInternal,
|
||||
};
|
||||
}, [api]);
|
||||
},
|
||||
{ fallback: <PanelLoader /> }
|
||||
);
|
||||
|
||||
const searchErrorDisplay = renderSearchError(error);
|
||||
|
||||
const actions = searchErrorDisplay?.actions ?? [];
|
||||
if (isEditable) {
|
||||
actions.push(
|
||||
<EuiButtonEmpty aria-label={ariaLabel} onClick={handleErrorClick} size="s">
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
searchErrorDisplay?.body ?? (
|
||||
<EuiText size="s" css={errorTextStyle}>
|
||||
<Markdown data-test-subj="errorMessageMarkdown" readOnly>
|
||||
{error.message?.length
|
||||
? error.message
|
||||
: i18n.translate('presentationPanel.emptyErrorMessage', {
|
||||
defaultMessage: 'Error',
|
||||
})}
|
||||
</Markdown>
|
||||
</EuiText>
|
||||
)
|
||||
}
|
||||
data-test-subj="embeddableStackError"
|
||||
iconType="warning"
|
||||
iconColor="danger"
|
||||
layout="vertical"
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export function PresentationPanelError(props: PresentationPanelErrorProps) {
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { PresentationPanelError } from './presentation_panel_error';
|
||||
import { PresentationPanelErrorInternal } from './presentation_panel_error_internal';
|
||||
|
||||
describe('PresentationPanelError', () => {
|
||||
describe('PresentationPanelErrorInternal', () => {
|
||||
test('should display error', async () => {
|
||||
render(<PresentationPanelError error={new Error('Simulated error')} />);
|
||||
render(<PresentationPanelErrorInternal error={new Error('Simulated error')} />);
|
||||
await waitFor(() => screen.getByTestId('errorMessageMarkdown'));
|
||||
});
|
||||
|
||||
test('should display error with empty message', async () => {
|
||||
render(<PresentationPanelError error={new Error('')} />);
|
||||
render(<PresentationPanelErrorInternal error={new Error('')} />);
|
||||
await waitFor(() => screen.getByTestId('errorMessageMarkdown'));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiEmptyPrompt, EuiText } from '@elastic/eui';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
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 { i18n } from '@kbn/i18n';
|
||||
import { useErrorTextStyle } from '@kbn/react-hooks';
|
||||
import { ActionExecutionMeta } from '@kbn/ui-actions-plugin/public';
|
||||
import { getErrorCallToAction } from './presentation_panel_strings';
|
||||
import { DefaultPresentationPanelApi } from './types';
|
||||
import { uiActions } from '../kibana_services';
|
||||
import { executeEditPanelAction } from '../panel_actions/edit_panel_action/execute_edit_action';
|
||||
import { ACTION_EDIT_PANEL } from '../panel_actions/edit_panel_action/constants';
|
||||
import { CONTEXT_MENU_TRIGGER } from '../panel_actions';
|
||||
|
||||
export interface PresentationPanelErrorProps {
|
||||
error: ErrorLike;
|
||||
api?: DefaultPresentationPanelApi;
|
||||
}
|
||||
|
||||
export const PresentationPanelErrorInternal = ({ api, error }: PresentationPanelErrorProps) => {
|
||||
const errorTextStyle = useErrorTextStyle();
|
||||
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const handleErrorClick = useMemo(
|
||||
() => (isEditable ? () => executeEditPanelAction(api) : undefined),
|
||||
[api, isEditable]
|
||||
);
|
||||
|
||||
const [label, setLabel] = useState('');
|
||||
useEffect(() => {
|
||||
if (!isEditable) {
|
||||
setLabel('');
|
||||
return;
|
||||
}
|
||||
|
||||
const canceled = false;
|
||||
uiActions
|
||||
.getAction(ACTION_EDIT_PANEL)
|
||||
.then((action) => {
|
||||
if (canceled) return;
|
||||
setLabel(
|
||||
action?.getDisplayName({
|
||||
embeddable: api,
|
||||
trigger: { id: CONTEXT_MENU_TRIGGER },
|
||||
} as EmbeddableApiContext & ActionExecutionMeta)
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore action not found
|
||||
});
|
||||
}, [api, isEditable]);
|
||||
|
||||
const panelTitle = useStateFromPublishingSubject(api?.panelTitle);
|
||||
const ariaLabel = useMemo(
|
||||
() => (panelTitle ? getErrorCallToAction(panelTitle) : label),
|
||||
[label, panelTitle]
|
||||
);
|
||||
|
||||
// Get initial editable state from action and subscribe to changes.
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
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({
|
||||
embeddable: api,
|
||||
trigger: { id: CONTEXT_MENU_TRIGGER },
|
||||
} as EmbeddableApiContext & ActionExecutionMeta);
|
||||
if (canceled) return;
|
||||
setIsEditable(initiallyCompatible);
|
||||
|
||||
subscription.add(
|
||||
editPanelAction?.subscribeToCompatibilityChanges?.({ embeddable: api }, (isCompatible) => {
|
||||
if (!canceled) setIsEditable(isCompatible);
|
||||
})
|
||||
);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
const searchErrorDisplay = renderSearchError(error);
|
||||
|
||||
const actions = searchErrorDisplay?.actions ?? [];
|
||||
if (isEditable) {
|
||||
actions.push(
|
||||
<EuiButtonEmpty aria-label={ariaLabel} onClick={handleErrorClick} size="s">
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
searchErrorDisplay?.body ?? (
|
||||
<EuiText size="s" css={errorTextStyle}>
|
||||
<Markdown data-test-subj="errorMessageMarkdown" readOnly>
|
||||
{error.message?.length
|
||||
? error.message
|
||||
: i18n.translate('presentationPanel.emptyErrorMessage', {
|
||||
defaultMessage: 'Error',
|
||||
})}
|
||||
</Markdown>
|
||||
</EuiText>
|
||||
)
|
||||
}
|
||||
data-test-subj="embeddableStackError"
|
||||
iconType="warning"
|
||||
iconColor="danger"
|
||||
layout="vertical"
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -18,7 +18,7 @@ import classNames from 'classnames';
|
|||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { PresentationPanelHeader } from './panel_header/presentation_panel_header';
|
||||
import { PresentationPanelHoverActions } from './panel_header/presentation_panel_hover_actions';
|
||||
import { PresentationPanelError } from './presentation_panel_error';
|
||||
import { PresentationPanelErrorInternal } from './presentation_panel_error_internal';
|
||||
import { DefaultPresentationPanelApi, PresentationPanelInternalProps } from './types';
|
||||
|
||||
export const PresentationPanelInternal = <
|
||||
|
@ -147,7 +147,7 @@ export const PresentationPanelInternal = <
|
|||
data-test-subj="embeddableError"
|
||||
justifyContent="center"
|
||||
>
|
||||
<PresentationPanelError api={api} error={blockingError} />
|
||||
<PresentationPanelErrorInternal api={api} error={blockingError} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{!initialLoadComplete && <PanelLoader />}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss
|
|||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import { setKibanaServices } from './kibana_services';
|
||||
import { registerActions } from './panel_actions/panel_actions';
|
||||
import { registerActions } from './panel_actions/register_actions';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface PresentationPanelSetup {}
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
"@kbn/panel-loader",
|
||||
"@kbn/search-errors",
|
||||
"@kbn/shared-ux-markdown",
|
||||
"@kbn/react-hooks"
|
||||
"@kbn/react-hooks",
|
||||
"@kbn/shared-ux-utility"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import { AggregateQuery, Filter, FilterStateStore, Query } from '@kbn/es-query';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
@ -44,8 +44,10 @@ const mockedEditPanelAction = {
|
|||
execute: jest.fn(),
|
||||
isCompatible: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
jest.mock('@kbn/presentation-panel-plugin/public', () => ({
|
||||
getEditPanelAction: () => mockedEditPanelAction,
|
||||
jest.mock('../services/kibana_services', () => ({
|
||||
uiActionsService: {
|
||||
getAction: async () => mockedEditPanelAction,
|
||||
},
|
||||
}));
|
||||
|
||||
describe('filters notification popover', () => {
|
||||
|
@ -133,6 +135,8 @@ describe('filters notification popover', () => {
|
|||
await renderAndOpenPopover();
|
||||
const editButton = await screen.findByTestId('filtersNotificationModal__editButton');
|
||||
await userEvent.click(editButton);
|
||||
expect(mockedEditPanelAction.execute).toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(mockedEditPanelAction.execute).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -23,13 +23,17 @@ import {
|
|||
|
||||
import { css } from '@emotion/react';
|
||||
import { AggregateQuery, getAggregateQueryMode, isOfQueryType } from '@kbn/es-query';
|
||||
import { getEditPanelAction } from '@kbn/presentation-panel-plugin/public';
|
||||
import { ACTION_EDIT_PANEL } from '@kbn/presentation-panel-plugin/public';
|
||||
import { FilterItems } from '@kbn/unified-search-plugin/public';
|
||||
import {
|
||||
EmbeddableApiContext,
|
||||
apiCanLockHoverActions,
|
||||
getViewModeSubject,
|
||||
useBatchedOptionalPublishingSubjects,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { ActionExecutionMeta } from '@kbn/ui-actions-plugin/public';
|
||||
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
import { uiActionsService } from '../services/kibana_services';
|
||||
import { dashboardFilterNotificationActionStrings } from './_dashboard_actions_strings';
|
||||
import { FiltersNotificationActionApi } from './filters_notification_action';
|
||||
|
||||
|
@ -37,12 +41,23 @@ export function FiltersNotificationPopover({ api }: { api: FiltersNotificationAc
|
|||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [disableEditbutton, setDisableEditButton] = useState(false);
|
||||
|
||||
const editPanelAction = getEditPanelAction();
|
||||
|
||||
const filters = useMemo(() => api.filters$?.value, [api]);
|
||||
const displayName = dashboardFilterNotificationActionStrings.getDisplayName();
|
||||
const canEditUnifiedSearch = api.canEditUnifiedSearch?.() ?? true;
|
||||
|
||||
const executeEditAction = useCallback(async () => {
|
||||
try {
|
||||
const action = await uiActionsService.getAction(ACTION_EDIT_PANEL);
|
||||
action.execute({
|
||||
embeddable: api,
|
||||
trigger: { id: CONTEXT_MENU_TRIGGER },
|
||||
} as EmbeddableApiContext & ActionExecutionMeta);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unable to execute edit action, Error: ', error.message);
|
||||
}
|
||||
}, [api]);
|
||||
|
||||
const { queryString, queryLanguage } = useMemo(() => {
|
||||
const query = api.query$?.value;
|
||||
if (!query) return {};
|
||||
|
@ -141,7 +156,7 @@ export function FiltersNotificationPopover({ api }: { api: FiltersNotificationAc
|
|||
data-test-subj={'filtersNotificationModal__editButton'}
|
||||
size="s"
|
||||
fill
|
||||
onClick={() => editPanelAction.execute({ embeddable: api })}
|
||||
onClick={executeEditAction}
|
||||
>
|
||||
{dashboardFilterNotificationActionStrings.getEditButtonTitle()}
|
||||
</EuiButton>
|
||||
|
|
|
@ -335,7 +335,7 @@ export function getDiscoverStateContainer({
|
|||
|
||||
services.dataViews.clearInstanceCache(prevDataView.id);
|
||||
|
||||
updateFiltersReferences({
|
||||
await updateFiltersReferences({
|
||||
prevDataView,
|
||||
nextDataView,
|
||||
services,
|
||||
|
|
|
@ -15,7 +15,7 @@ import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
|||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
|
||||
export const updateFiltersReferences = ({
|
||||
export const updateFiltersReferences = async ({
|
||||
prevDataView,
|
||||
nextDataView,
|
||||
services: { uiActions },
|
||||
|
@ -25,7 +25,7 @@ export const updateFiltersReferences = ({
|
|||
services: DiscoverServices;
|
||||
}) => {
|
||||
const trigger = uiActions.getTrigger(UPDATE_FILTER_REFERENCES_TRIGGER);
|
||||
const action = uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
const action = await uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
action?.execute({
|
||||
trigger,
|
||||
fromDataView: prevDataView.id,
|
||||
|
|
|
@ -50,7 +50,7 @@ export const FloatingActions: FC<FloatingActionsProps> = ({
|
|||
useEffect(() => {
|
||||
if (!api) return;
|
||||
|
||||
let mounted = true;
|
||||
let canceled = false;
|
||||
const context = {
|
||||
embeddable: api,
|
||||
trigger: panelHoverTrigger,
|
||||
|
@ -74,7 +74,7 @@ export const FloatingActions: FC<FloatingActionsProps> = ({
|
|||
const subscriptions = new Subscription();
|
||||
|
||||
const handleActionCompatibilityChange = (isCompatible: boolean, action: Action) => {
|
||||
if (!mounted) return;
|
||||
if (canceled) return;
|
||||
setFloatingActions((currentActions) => {
|
||||
const newActions: FloatingActionItem[] = currentActions
|
||||
?.filter((current) => current.id !== action.id)
|
||||
|
@ -88,13 +88,12 @@ export const FloatingActions: FC<FloatingActionsProps> = ({
|
|||
|
||||
(async () => {
|
||||
const actions = await getActions();
|
||||
if (!mounted) return;
|
||||
if (canceled) return;
|
||||
setFloatingActions(actions);
|
||||
|
||||
const frequentlyChangingActions = uiActionsService.getFrequentlyChangingActionsForTrigger(
|
||||
PANEL_HOVER_TRIGGER,
|
||||
context
|
||||
);
|
||||
const frequentlyChangingActions =
|
||||
await uiActionsService.getFrequentlyChangingActionsForTrigger(PANEL_HOVER_TRIGGER, context);
|
||||
if (canceled) return;
|
||||
|
||||
for (const action of frequentlyChangingActions) {
|
||||
subscriptions.add(
|
||||
|
@ -104,7 +103,7 @@ export const FloatingActions: FC<FloatingActionsProps> = ({
|
|||
})();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
canceled = true;
|
||||
subscriptions.unsubscribe();
|
||||
};
|
||||
}, [api, viewMode, disabledActions]);
|
||||
|
|
|
@ -33,6 +33,7 @@ const createStartContract = (): Start => {
|
|||
attachAction: jest.fn(),
|
||||
unregisterAction: jest.fn(),
|
||||
addTriggerAction: jest.fn(),
|
||||
addTriggerActionAsync: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
detachAction: jest.fn(),
|
||||
executeTriggerActions: jest.fn(),
|
||||
|
@ -41,14 +42,16 @@ const createStartContract = (): Start => {
|
|||
hasAction: jest.fn(),
|
||||
getTrigger: jest.fn(),
|
||||
hasTrigger: jest.fn(),
|
||||
getTriggerActions: jest.fn((id: string) => []),
|
||||
getTriggerActions: jest.fn(async (id: string) => []),
|
||||
getTriggerCompatibleActions: jest.fn((triggerId: string, context: object) =>
|
||||
Promise.resolve([] as Array<Action<object>>)
|
||||
),
|
||||
getFrequentlyChangingActionsForTrigger: jest.fn(
|
||||
(triggerId: string, context: object) => [] as Array<FrequentCompatibilityChangeAction<object>>
|
||||
async (triggerId: string, context: object) =>
|
||||
[] as Array<FrequentCompatibilityChangeAction<object>>
|
||||
),
|
||||
registerAction: jest.fn(),
|
||||
registerActionAsync: jest.fn(),
|
||||
registerTrigger: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ describe('UiActionsService', () => {
|
|||
isCompatible: async () => true,
|
||||
};
|
||||
|
||||
test('returns actions set on trigger', () => {
|
||||
test('returns actions set on trigger', async () => {
|
||||
const service = new UiActionsService();
|
||||
|
||||
service.registerAction(action1);
|
||||
|
@ -139,19 +139,19 @@ describe('UiActionsService', () => {
|
|||
title: 'baz',
|
||||
});
|
||||
|
||||
const list0 = service.getTriggerActions(FOO_TRIGGER);
|
||||
const list0 = await service.getTriggerActions(FOO_TRIGGER);
|
||||
|
||||
expect(list0).toHaveLength(0);
|
||||
|
||||
service.addTriggerAction(FOO_TRIGGER, action1);
|
||||
const list1 = service.getTriggerActions(FOO_TRIGGER);
|
||||
const list1 = await service.getTriggerActions(FOO_TRIGGER);
|
||||
|
||||
expect(list1).toHaveLength(1);
|
||||
expect(list1[0]).toBeInstanceOf(ActionInternal);
|
||||
expect(list1[0].id).toBe(action1.id);
|
||||
|
||||
service.addTriggerAction(FOO_TRIGGER, action2);
|
||||
const list2 = service.getTriggerActions(FOO_TRIGGER);
|
||||
const list2 = await service.getTriggerActions(FOO_TRIGGER);
|
||||
|
||||
expect(list2).toHaveLength(2);
|
||||
expect(!!list2.find(({ id }: { id: string }) => id === 'action1')).toBe(true);
|
||||
|
@ -171,7 +171,8 @@ describe('UiActionsService', () => {
|
|||
service.registerAction(helloWorldAction);
|
||||
|
||||
expect(actions.size - length).toBe(1);
|
||||
expect(actions.get(helloWorldAction.id)!.id).toBe(helloWorldAction.id);
|
||||
const action = await actions.get(helloWorldAction.id)?.();
|
||||
expect(action?.id).toBe(helloWorldAction.id);
|
||||
});
|
||||
|
||||
test('getTriggerCompatibleActions returns attached actions', async () => {
|
||||
|
@ -288,7 +289,7 @@ describe('UiActionsService', () => {
|
|||
expect(trigger2.id).toBe(FOO_TRIGGER);
|
||||
});
|
||||
|
||||
test('forked service preserves trigger-to-actions mapping', () => {
|
||||
test('forked service preserves trigger-to-actions mapping', async () => {
|
||||
const service1 = new UiActionsService();
|
||||
|
||||
service1.registerTrigger({
|
||||
|
@ -299,8 +300,8 @@ describe('UiActionsService', () => {
|
|||
|
||||
const service2 = service1.fork();
|
||||
|
||||
const actions1 = service1.getTriggerActions(FOO_TRIGGER);
|
||||
const actions2 = service2.getTriggerActions(FOO_TRIGGER);
|
||||
const actions1 = await service1.getTriggerActions(FOO_TRIGGER);
|
||||
const actions2 = await service2.getTriggerActions(FOO_TRIGGER);
|
||||
|
||||
expect(actions1).toHaveLength(1);
|
||||
expect(actions2).toHaveLength(1);
|
||||
|
@ -308,7 +309,7 @@ describe('UiActionsService', () => {
|
|||
expect(actions2[0].id).toBe(testAction1.id);
|
||||
});
|
||||
|
||||
test('new attachments in fork do not appear in original service', () => {
|
||||
test('new attachments in fork do not appear in original service', async () => {
|
||||
const service1 = new UiActionsService();
|
||||
|
||||
service1.registerTrigger({
|
||||
|
@ -320,16 +321,16 @@ describe('UiActionsService', () => {
|
|||
|
||||
const service2 = service1.fork();
|
||||
|
||||
expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
|
||||
service2.addTriggerAction(FOO_TRIGGER, testAction2);
|
||||
|
||||
expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(2);
|
||||
expect(await service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('new attachments in original service do not appear in fork', () => {
|
||||
test('new attachments in original service do not appear in fork', async () => {
|
||||
const service1 = new UiActionsService();
|
||||
|
||||
service1.registerTrigger({
|
||||
|
@ -341,13 +342,13 @@ describe('UiActionsService', () => {
|
|||
|
||||
const service2 = service1.fork();
|
||||
|
||||
expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
|
||||
service1.addTriggerAction(FOO_TRIGGER, testAction2);
|
||||
|
||||
expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(2);
|
||||
expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
expect(await service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(2);
|
||||
expect(await service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -372,7 +373,7 @@ describe('UiActionsService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('can register action', () => {
|
||||
test('can register action', async () => {
|
||||
const actions: ActionRegistry = new Map();
|
||||
const service = new UiActionsService({ actions });
|
||||
|
||||
|
@ -381,13 +382,13 @@ describe('UiActionsService', () => {
|
|||
order: 13,
|
||||
} as unknown as ActionDefinition);
|
||||
|
||||
expect(actions.get(ACTION_HELLO_WORLD)).toMatchObject({
|
||||
expect(await actions.get(ACTION_HELLO_WORLD)?.()).toMatchObject({
|
||||
id: ACTION_HELLO_WORLD,
|
||||
order: 13,
|
||||
});
|
||||
});
|
||||
|
||||
test('can attach an action to a trigger', () => {
|
||||
test('can attach an action to a trigger', async () => {
|
||||
const service = new UiActionsService();
|
||||
|
||||
const trigger: Trigger = {
|
||||
|
@ -401,13 +402,13 @@ describe('UiActionsService', () => {
|
|||
service.registerTrigger(trigger);
|
||||
service.addTriggerAction(MY_TRIGGER, action);
|
||||
|
||||
const actions = service.getTriggerActions(trigger.id);
|
||||
const actions = await service.getTriggerActions(trigger.id);
|
||||
|
||||
expect(actions.length).toBe(1);
|
||||
expect(actions[0].id).toBe(ACTION_HELLO_WORLD);
|
||||
});
|
||||
|
||||
test('can detach an action from a trigger', () => {
|
||||
test('can detach an action from a trigger', async () => {
|
||||
const service = new UiActionsService();
|
||||
|
||||
const trigger: Trigger = {
|
||||
|
@ -423,7 +424,7 @@ describe('UiActionsService', () => {
|
|||
service.addTriggerAction(trigger.id, action);
|
||||
service.detachAction(trigger.id, action.id);
|
||||
|
||||
const actions2 = service.getTriggerActions(trigger.id);
|
||||
const actions2 = await service.getTriggerActions(trigger.id);
|
||||
expect(actions2).toEqual([]);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import type { Trigger } from '@kbn/ui-actions-browser/src/triggers';
|
||||
import { asyncMap } from '@kbn/std';
|
||||
import { TriggerRegistry, ActionRegistry, TriggerToActionsRegistry } from '../types';
|
||||
import {
|
||||
ActionInternal,
|
||||
|
@ -70,6 +71,11 @@ export class UiActionsService {
|
|||
return trigger.contract;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use `plugins.uiActions.registerActionAsync` instead.
|
||||
*/
|
||||
public readonly registerAction = <Context extends object>(
|
||||
definition: ActionDefinition<Context>
|
||||
): Action<Context> => {
|
||||
|
@ -79,11 +85,25 @@ export class UiActionsService {
|
|||
|
||||
const action = new ActionInternal(definition);
|
||||
|
||||
this.actions.set(action.id, action as unknown as ActionInternal<object>);
|
||||
this.actions.set(action.id, async () => action as unknown as ActionInternal<object>);
|
||||
|
||||
return action;
|
||||
};
|
||||
|
||||
public readonly registerActionAsync = <Context extends object>(
|
||||
id: string,
|
||||
getDefinition: () => Promise<ActionDefinition<Context>>
|
||||
) => {
|
||||
if (this.actions.has(id)) {
|
||||
throw new Error(`Action [action.id = ${id}] already registered.`);
|
||||
}
|
||||
|
||||
this.actions.set(id, async () => {
|
||||
const action = new ActionInternal(await getDefinition());
|
||||
return action as unknown as ActionInternal<object>;
|
||||
});
|
||||
};
|
||||
|
||||
public readonly unregisterAction = (actionId: string): void => {
|
||||
if (!this.actions.has(actionId)) {
|
||||
throw new Error(`Action [action.id = ${actionId}] is not registered.`);
|
||||
|
@ -130,40 +150,56 @@ export class UiActionsService {
|
|||
};
|
||||
|
||||
/**
|
||||
* `addTriggerAction` is similar to `attachAction` as it attaches action to a
|
||||
* trigger, but it also registers the action, if it has not been registered, yet.
|
||||
* @deprecated
|
||||
*
|
||||
* Use `plugins.uiActions.addTriggerActionAsync` instead.
|
||||
*/
|
||||
public readonly addTriggerAction = (triggerId: string, action: ActionDefinition<any>): void => {
|
||||
if (!this.actions.has(action.id)) this.registerAction(action);
|
||||
this.attachAction(triggerId, action.id);
|
||||
};
|
||||
|
||||
public readonly getAction = (id: string): Action => {
|
||||
if (!this.actions.has(id)) {
|
||||
/**
|
||||
* `addTriggerAction` is similar to `attachAction` as it attaches action to a
|
||||
* trigger, but it also registers the action, if it has not been registered, yet.
|
||||
*/
|
||||
public readonly addTriggerActionAsync = (
|
||||
triggerId: string,
|
||||
actionId: string,
|
||||
getDefinition: () => Promise<ActionDefinition<any>>
|
||||
): void => {
|
||||
if (!this.actions.has(actionId)) this.registerActionAsync(actionId, getDefinition);
|
||||
this.attachAction(triggerId, actionId);
|
||||
};
|
||||
|
||||
public readonly getAction = async (id: string): Promise<Action> => {
|
||||
const getAction = this.actions.get(id);
|
||||
if (!getAction) {
|
||||
throw new Error(`Action [action.id = ${id}] not registered.`);
|
||||
}
|
||||
|
||||
return this.actions.get(id)! as Action;
|
||||
return (await getAction()) as Action;
|
||||
};
|
||||
|
||||
public readonly getTriggerActions = (triggerId: string): Action[] => {
|
||||
public readonly getTriggerActions = async (triggerId: string): Promise<Action[]> => {
|
||||
// This line checks if trigger exists, otherwise throws.
|
||||
this.getTrigger!(triggerId);
|
||||
|
||||
const actionIds = this.triggerToActions.get(triggerId);
|
||||
const actionIds = this.triggerToActions.get(triggerId) ?? [];
|
||||
|
||||
const actions = actionIds!
|
||||
.map((actionId) => this.actions.get(actionId) as ActionInternal)
|
||||
.filter(Boolean);
|
||||
const actions = await asyncMap(
|
||||
actionIds,
|
||||
async (actionId) => (await this.actions.get(actionId)?.()) as ActionInternal
|
||||
);
|
||||
|
||||
return actions as Action[];
|
||||
return actions.filter(Boolean);
|
||||
};
|
||||
|
||||
public readonly getTriggerCompatibleActions = async (
|
||||
triggerId: string,
|
||||
context: object
|
||||
): Promise<Action[]> => {
|
||||
const actions = this.getTriggerActions!(triggerId);
|
||||
const actions = await this.getTriggerActions(triggerId);
|
||||
const isCompatibles = await Promise.all(
|
||||
actions.map((action) =>
|
||||
action.isCompatible({
|
||||
|
@ -180,11 +216,11 @@ export class UiActionsService {
|
|||
}, []);
|
||||
};
|
||||
|
||||
public readonly getFrequentlyChangingActionsForTrigger = (
|
||||
public readonly getFrequentlyChangingActionsForTrigger = async (
|
||||
triggerId: string,
|
||||
context: object
|
||||
): FrequentCompatibilityChangeAction[] => {
|
||||
return this.getTriggerActions!(triggerId).filter((action) => {
|
||||
): Promise<FrequentCompatibilityChangeAction[]> => {
|
||||
return (await this.getTriggerActions(triggerId)).filter((action) => {
|
||||
return (
|
||||
Boolean(action.subscribeToCompatibilityChanges) &&
|
||||
action.couldBecomeCompatible?.({
|
||||
|
|
|
@ -24,7 +24,7 @@ const action2: ActionDefinition = {
|
|||
execute: async () => {},
|
||||
};
|
||||
|
||||
test('returns actions set on trigger', () => {
|
||||
test('returns actions set on trigger', async () => {
|
||||
const { setup, doStart } = uiActionsPluginMock.createPlugin();
|
||||
setup.registerAction(action1);
|
||||
setup.registerAction(action2);
|
||||
|
@ -35,19 +35,19 @@ test('returns actions set on trigger', () => {
|
|||
});
|
||||
|
||||
const start = doStart();
|
||||
const list0 = start.getTriggerActions('trigger');
|
||||
const list0 = await start.getTriggerActions('trigger');
|
||||
|
||||
expect(list0).toHaveLength(0);
|
||||
|
||||
setup.addTriggerAction('trigger', action1);
|
||||
const list1 = start.getTriggerActions('trigger');
|
||||
const list1 = await start.getTriggerActions('trigger');
|
||||
|
||||
expect(list1).toHaveLength(1);
|
||||
expect(list1[0]).toBeInstanceOf(ActionInternal);
|
||||
expect(list1[0].id).toBe(action1.id);
|
||||
|
||||
setup.addTriggerAction('trigger', action2);
|
||||
const list2 = start.getTriggerActions('trigger');
|
||||
const list2 = await start.getTriggerActions('trigger');
|
||||
|
||||
expect(list2).toHaveLength(2);
|
||||
expect(!!list2.find(({ id }: { id: string }) => id === 'action1')).toBe(true);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ActionInternal } from './actions/action_internal';
|
|||
import { TriggerInternal } from './triggers/trigger_internal';
|
||||
|
||||
export type TriggerRegistry = Map<string, TriggerInternal<object>>;
|
||||
export type ActionRegistry = Map<string, ActionInternal>;
|
||||
export type ActionRegistry = Map<string, () => Promise<ActionInternal>>;
|
||||
export type TriggerToActionsRegistry = Map<string, string[]>;
|
||||
|
||||
export interface VisualizeFieldContext {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@kbn/expressions-plugin",
|
||||
"@kbn/react-kibana-context-render",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/std",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -314,7 +314,7 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']);
|
||||
|
||||
const createdAction = actions.values().next().value;
|
||||
const createdAction = await actions.values().next().value();
|
||||
|
||||
expect(createdAction.grouping).toBe(dynamicActionGrouping);
|
||||
});
|
||||
|
@ -477,7 +477,7 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
expect(actions.size).toBe(1);
|
||||
|
||||
const registeredAction1 = actions.values().next().value;
|
||||
const registeredAction1 = await actions.values().next().value();
|
||||
|
||||
expect(registeredAction1.getDisplayName()).toBe('Action 3');
|
||||
|
||||
|
@ -491,7 +491,7 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
expect(actions.size).toBe(1);
|
||||
|
||||
const registeredAction2 = actions.values().next().value;
|
||||
const registeredAction2 = await actions.values().next().value();
|
||||
|
||||
expect(registeredAction2.getDisplayName()).toBe('foo');
|
||||
});
|
||||
|
@ -600,7 +600,7 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
expect(actions.size).toBe(1);
|
||||
|
||||
const registeredAction1 = actions.values().next().value;
|
||||
const registeredAction1 = await actions.values().next().value();
|
||||
|
||||
expect(registeredAction1.getDisplayName()).toBe('Action 3');
|
||||
|
||||
|
@ -614,7 +614,7 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
expect(actions.size).toBe(1);
|
||||
|
||||
const registeredAction2 = actions.values().next().value;
|
||||
const registeredAction2 = await actions.values().next().value();
|
||||
|
||||
expect(registeredAction2.getDisplayName()).toBe('Action 3');
|
||||
});
|
||||
|
@ -733,10 +733,10 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
await manager.start();
|
||||
|
||||
expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(2);
|
||||
expect(await uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(2);
|
||||
expect(await storage.list()).toEqual([event1, event3, event2]);
|
||||
|
||||
await manager.stop();
|
||||
expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(0);
|
||||
expect(await uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -278,7 +278,10 @@ export const getFieldStatsChartEmbeddableFactory = (
|
|||
}
|
||||
};
|
||||
|
||||
const addFilters = (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => {
|
||||
const addFilters = async (
|
||||
filters: Filter[],
|
||||
actionId: string = ACTION_GLOBAL_APPLY_FILTER
|
||||
) => {
|
||||
if (!pluginStart.uiActions) {
|
||||
toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR);
|
||||
return;
|
||||
|
@ -298,7 +301,7 @@ export const getFieldStatsChartEmbeddableFactory = (
|
|||
filters,
|
||||
};
|
||||
try {
|
||||
const action = pluginStart.uiActions.getAction(actionId);
|
||||
const action = await pluginStart.uiActions.getAction(actionId);
|
||||
action.execute(executeContext);
|
||||
} catch (error) {
|
||||
toasts.addWarning(ERROR_MSG.APPLY_FILTER_ERR);
|
||||
|
|
|
@ -115,7 +115,7 @@ export const createIndexPatternService = ({
|
|||
applyImmediately: true,
|
||||
});
|
||||
const trigger = uiActions.getTrigger(UPDATE_FILTER_REFERENCES_TRIGGER);
|
||||
const action = uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
const action = await uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
|
||||
action?.execute({
|
||||
trigger,
|
||||
|
|
|
@ -227,7 +227,7 @@ export function renameIndexPattern({
|
|||
};
|
||||
}
|
||||
|
||||
export function triggerActionOnIndexPatternChange({
|
||||
export async function triggerActionOnIndexPatternChange({
|
||||
state,
|
||||
layerId,
|
||||
uiActions,
|
||||
|
@ -243,7 +243,7 @@ export function triggerActionOnIndexPatternChange({
|
|||
const toDataView = indexPatternId;
|
||||
|
||||
const trigger = uiActions.getTrigger(UPDATE_FILTER_REFERENCES_TRIGGER);
|
||||
const action = uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
const action = await uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
|
||||
action?.execute({
|
||||
trigger,
|
||||
|
|
|
@ -165,7 +165,7 @@ export function LayerPanels(
|
|||
);
|
||||
|
||||
const onRemoveLayer = useCallback(
|
||||
(layerToRemoveId: string) => {
|
||||
async (layerToRemoveId: string) => {
|
||||
const datasourcePublicAPI = props.framePublicAPI.datasourceLayers?.[layerToRemoveId];
|
||||
const datasourceId = datasourcePublicAPI?.datasourceId;
|
||||
|
||||
|
@ -173,7 +173,7 @@ export function LayerPanels(
|
|||
const layerDatasource = datasourceMap[datasourceId];
|
||||
const layerDatasourceState = datasourceStates?.[datasourceId]?.state;
|
||||
const trigger = props.uiActions.getTrigger(UPDATE_FILTER_REFERENCES_TRIGGER);
|
||||
const action = props.uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
const action = await props.uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION);
|
||||
|
||||
action?.execute({
|
||||
trigger,
|
||||
|
|
|
@ -60,7 +60,7 @@ export interface RenderTooltipContentParams {
|
|||
layerId: string;
|
||||
featureId?: string | number;
|
||||
}) => Geometry | null;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
}
|
||||
|
||||
export type RenderToolTipContent = (params: RenderTooltipContentParams) => JSX.Element;
|
||||
|
|
|
@ -35,7 +35,7 @@ export interface Props {
|
|||
addFilters: ((filters: Filter[], actionId: string) => Promise<void>) | null;
|
||||
getFilterActions?: () => Promise<Action[]>;
|
||||
getActionContext?: () => ActionExecutionContext;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
isMapLoading: boolean;
|
||||
cancelAllInFlightRequests: () => void;
|
||||
exitFullScreen: () => void;
|
||||
|
|
|
@ -73,7 +73,7 @@ export interface Props {
|
|||
addFilters: ((filters: Filter[], actionId: string) => Promise<void>) | null;
|
||||
getFilterActions?: () => Promise<Action[]>;
|
||||
getActionContext?: () => ActionExecutionContext;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
renderTooltipContent?: RenderToolTipContent;
|
||||
timeslice?: Timeslice;
|
||||
featureModeActive: boolean;
|
||||
|
|
|
@ -40,7 +40,7 @@ interface Props {
|
|||
addFilters: ((filters: Filter[], actionId: string) => Promise<void>) | null;
|
||||
getFilterActions?: () => Promise<Action[]>;
|
||||
getActionContext?: () => ActionExecutionContext;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
showFilterActions: (view: ReactNode) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ interface Props {
|
|||
addFilters: ((filters: Filter[], actionId: string) => Promise<void>) | null;
|
||||
getFilterActions?: () => Promise<Action[]>;
|
||||
getActionContext?: () => ActionExecutionContext;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
closeTooltip: () => void;
|
||||
features: TooltipFeature[];
|
||||
isLocked: boolean;
|
||||
|
|
|
@ -62,7 +62,7 @@ export interface Props {
|
|||
mbMap: MbMap;
|
||||
openOnClickTooltip: (tooltipState: TooltipState) => void;
|
||||
openOnHoverTooltip: (tooltipState: TooltipState) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
openTooltips: TooltipState[];
|
||||
renderTooltipContent?: RenderToolTipContent;
|
||||
updateOpenTooltips: (openTooltips: TooltipState[]) => void;
|
||||
|
|
|
@ -38,7 +38,7 @@ interface Props {
|
|||
}) => Geometry | null;
|
||||
location: [number, number];
|
||||
mbMap: MbMap;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => Promise<void>;
|
||||
renderTooltipContent?: RenderToolTipContent;
|
||||
executionContext: KibanaExecutionContext;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export function initializeActionHandlers(getApi: () => MapApi | undefined) {
|
|||
...getActionContext(),
|
||||
filters,
|
||||
};
|
||||
const action = getUiActions().getAction(actionId);
|
||||
const action = await getUiActions().getAction(actionId);
|
||||
if (!action) {
|
||||
throw new Error('Unable to apply filter, could not locate action');
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ export function initializeActionHandlers(getApi: () => MapApi | undefined) {
|
|||
);
|
||||
return [...filterActions, ...valueClickActions.filter(isUrlDrilldown)];
|
||||
},
|
||||
onSingleValueTrigger: (actionId: string, key: string, value: RawValue) => {
|
||||
const action = getUiActions().getAction(actionId);
|
||||
onSingleValueTrigger: async (actionId: string, key: string, value: RawValue) => {
|
||||
const action = await getUiActions().getAction(actionId);
|
||||
if (!action) {
|
||||
throw new Error('Unable to apply action, could not locate action');
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ export function initializeCrossPanelActions({
|
|||
}
|
||||
|
||||
// debounce to fix timing issue for dashboard with multiple maps with synchronized movement and filter by map extent enabled
|
||||
const setMapExtentFilter = _.debounce(() => {
|
||||
const setMapExtentFilter = _.debounce(async () => {
|
||||
const mapExtent = getMapExtent(savedMap.getStore().getState());
|
||||
const geoFieldNames = mapEmbeddablesSingleton.getGeoFieldNames();
|
||||
|
||||
|
@ -126,21 +126,21 @@ export function initializeCrossPanelActions({
|
|||
filters: [mapExtentFilter],
|
||||
controlledBy,
|
||||
};
|
||||
const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER);
|
||||
const action = await getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER);
|
||||
if (!action) {
|
||||
throw new Error('Unable to apply map extent filter, could not locate action');
|
||||
}
|
||||
action.execute(executeContext);
|
||||
}, 100);
|
||||
|
||||
function clearMapExtentFilter() {
|
||||
async function clearMapExtentFilter() {
|
||||
prevMapExtent = undefined;
|
||||
const executeContext = {
|
||||
...getActionContext(),
|
||||
filters: [],
|
||||
controlledBy,
|
||||
};
|
||||
const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER);
|
||||
const action = await getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER);
|
||||
if (!action) {
|
||||
throw new Error('Unable to apply map extent filter, could not locate action');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue