Remove UiComponent and related code (#148037)

## Summary

- Removes `UiComponent` and its usages.
- Fixes UI Actions TypeScript types.


### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Anton Dosov <dosantappdev@gmail.com>
Co-authored-by: Dima Arnautov <arnautov.dima@gmail.com>
This commit is contained in:
Vadim Kibana 2023-01-09 23:34:33 +01:00 committed by GitHub
parent 5921abbf35
commit 039ed991d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 366 additions and 827 deletions

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { ViewMode, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public';
import { DASHBOARD_CONTAINER_TYPE } from '@kbn/dashboard-plugin/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
@ -18,31 +18,30 @@ interface ActionContext {
export const ACTION_ADD_BOOK_TO_LIBRARY = 'ACTION_ADD_BOOK_TO_LIBRARY';
export const createAddBookToLibraryAction = () =>
createAction({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.addToLibrary', {
defaultMessage: 'Add Book To Library',
}),
id: ACTION_ADD_BOOK_TO_LIBRARY,
type: ACTION_ADD_BOOK_TO_LIBRARY,
order: 100,
getIconType: () => 'folderCheck',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
!embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsRefType();
embeddable.updateInput(newInput);
},
});
export const createAddBookToLibraryActionDefinition = () => ({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.addToLibrary', {
defaultMessage: 'Add Book To Library',
}),
id: ACTION_ADD_BOOK_TO_LIBRARY,
type: ACTION_ADD_BOOK_TO_LIBRARY,
order: 100,
getIconType: () => 'folderCheck',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
!embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsRefType();
embeddable.updateInput(newInput);
},
});

View file

@ -9,7 +9,6 @@
import React from 'react';
import { OverlayStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import {
ViewMode,
@ -39,61 +38,58 @@ interface ActionContext {
export const ACTION_EDIT_BOOK = 'ACTION_EDIT_BOOK';
export const createEditBookAction = (getStartServices: () => Promise<StartServices>) =>
createAction({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.edit', { defaultMessage: 'Edit Book' }),
id: ACTION_EDIT_BOOK,
type: ACTION_EDIT_BOOK,
order: 100,
getIconType: () => 'documents',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE && embeddable.getInput().viewMode === ViewMode.EDIT
);
},
execute: async ({ embeddable }: ActionContext) => {
const { openModal, getAttributeService, savedObjectsClient } = await getStartServices();
const attributeService = getAttributeService<BookSavedObjectAttributes>(BOOK_SAVED_OBJECT, {
saveMethod: async (attributes: BookSavedObjectAttributes, savedObjectId?: string) => {
if (savedObjectId) {
return savedObjectsClient.update(BOOK_EMBEDDABLE, savedObjectId, attributes);
}
return savedObjectsClient.create(BOOK_EMBEDDABLE, attributes);
},
checkForDuplicateTitle: (props: OnSaveProps) => {
return new Promise(() => {
return true;
});
},
});
const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => {
const newInput = await attributeService.wrapAttributes(
attributes,
useRefType,
embeddable.getExplicitInput()
);
if (!useRefType && (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId) {
// Set the saved object ID to null so that update input will remove the existing savedObjectId...
(newInput as BookByValueInput & { savedObjectId: unknown }).savedObjectId = null;
export const createEditBookActionDefinition = (getStartServices: () => Promise<StartServices>) => ({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.edit', { defaultMessage: 'Edit Book' }),
id: ACTION_EDIT_BOOK,
type: ACTION_EDIT_BOOK,
order: 100,
getIconType: () => 'documents',
isCompatible: async ({ embeddable }: ActionContext) => {
return embeddable.type === BOOK_EMBEDDABLE && embeddable.getInput().viewMode === ViewMode.EDIT;
},
execute: async ({ embeddable }: ActionContext) => {
const { openModal, getAttributeService, savedObjectsClient } = await getStartServices();
const attributeService = getAttributeService<BookSavedObjectAttributes>(BOOK_SAVED_OBJECT, {
saveMethod: async (attributes: BookSavedObjectAttributes, savedObjectId?: string) => {
if (savedObjectId) {
return savedObjectsClient.update(BOOK_EMBEDDABLE, savedObjectId, attributes);
}
embeddable.updateInput(newInput);
if (useRefType) {
// Ensures that any duplicate embeddables also register the changes. This mirrors the behavior of going back and forth between apps
embeddable.getRoot().reload();
}
};
const overlay = openModal(
toMountPoint(
<CreateEditBookComponent
savedObjectId={(embeddable.getInput() as BookByReferenceInput).savedObjectId}
attributes={embeddable.getOutput().attributes}
onSave={(attributes: BookSavedObjectAttributes, useRefType: boolean) => {
overlay.close();
onSave(attributes, useRefType);
}}
/>
)
return savedObjectsClient.create(BOOK_EMBEDDABLE, attributes);
},
checkForDuplicateTitle: (props: OnSaveProps) => {
return new Promise(() => {
return true;
});
},
});
const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => {
const newInput = await attributeService.wrapAttributes(
attributes,
useRefType,
embeddable.getExplicitInput()
);
},
});
if (!useRefType && (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId) {
// Set the saved object ID to null so that update input will remove the existing savedObjectId...
(newInput as BookByValueInput & { savedObjectId: unknown }).savedObjectId = null;
}
embeddable.updateInput(newInput);
if (useRefType) {
// Ensures that any duplicate embeddables also register the changes. This mirrors the behavior of going back and forth between apps
embeddable.getRoot().reload();
}
};
const overlay = openModal(
toMountPoint(
<CreateEditBookComponent
savedObjectId={(embeddable.getInput() as BookByReferenceInput).savedObjectId}
attributes={embeddable.getOutput().attributes}
onSave={(attributes: BookSavedObjectAttributes, useRefType: boolean) => {
overlay.close();
onSave(attributes, useRefType);
}}
/>
)
);
},
});

View file

@ -7,7 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { ViewMode, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public';
import { DASHBOARD_CONTAINER_TYPE } from '@kbn/dashboard-plugin/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
@ -18,31 +18,30 @@ interface ActionContext {
export const ACTION_UNLINK_BOOK_FROM_LIBRARY = 'ACTION_UNLINK_BOOK_FROM_LIBRARY';
export const createUnlinkBookFromLibraryAction = () =>
createAction({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.unlinkFromLibrary', {
defaultMessage: 'Unlink Book from Library Item',
}),
id: ACTION_UNLINK_BOOK_FROM_LIBRARY,
type: ACTION_UNLINK_BOOK_FROM_LIBRARY,
order: 100,
getIconType: () => 'folderExclamation',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsValueType();
embeddable.updateInput(newInput);
},
});
export const createUnlinkBookFromLibraryActionDefinition = () => ({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.unlinkFromLibrary', {
defaultMessage: 'Unlink Book from Library Item',
}),
id: ACTION_UNLINK_BOOK_FROM_LIBRARY,
type: ACTION_UNLINK_BOOK_FROM_LIBRARY,
order: 100,
getIconType: () => 'folderExclamation',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
embeddable.getRoot().isContainer &&
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
isReferenceOrValueEmbeddable(embeddable) &&
embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsValueType();
embeddable.updateInput(newInput);
},
});

View file

@ -41,14 +41,14 @@ import {
TodoRefEmbeddableFactory,
TodoRefEmbeddableFactoryDefinition,
} from './todo/todo_ref_embeddable_factory';
import { createEditBookAction } from './book/edit_book_action';
import { createEditBookActionDefinition } from './book/edit_book_action';
import { BOOK_EMBEDDABLE } from './book/book_embeddable';
import {
BookEmbeddableFactory,
BookEmbeddableFactoryDefinition,
} from './book/book_embeddable_factory';
import { createAddBookToLibraryAction } from './book/add_book_to_library_action';
import { createUnlinkBookFromLibraryAction } from './book/unlink_book_from_library_action';
import { createAddBookToLibraryActionDefinition } from './book/add_book_to_library_action';
import { createUnlinkBookFromLibraryActionDefinition } from './book/unlink_book_from_library_action';
import {
SIMPLE_EMBEDDABLE,
SimpleEmbeddableFactory,
@ -157,7 +157,7 @@ export class EmbeddableExamplesPlugin
}))
);
const editBookAction = createEditBookAction(async () => ({
const editBookAction = createEditBookActionDefinition(async () => ({
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
openModal: (await core.getStartServices())[0].overlays.openModal,
savedObjectsClient: (await core.getStartServices())[0].savedObjects.client,
@ -165,11 +165,11 @@ export class EmbeddableExamplesPlugin
deps.uiActions.registerAction(editBookAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, editBookAction.id);
const addBookToLibraryAction = createAddBookToLibraryAction();
const addBookToLibraryAction = createAddBookToLibraryActionDefinition();
deps.uiActions.registerAction(addBookToLibraryAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, addBookToLibraryAction.id);
const unlinkBookFromLibraryAction = createUnlinkBookFromLibraryAction();
const unlinkBookFromLibraryAction = createUnlinkBookFromLibraryActionDefinition();
deps.uiActions.registerAction(unlinkBookFromLibraryAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkBookFromLibraryAction.id);
}

View file

@ -9,7 +9,6 @@
import React from 'react';
import { EuiText, EuiModalBody, EuiButton } from '@elastic/eui';
import { OverlayStart } from '@kbn/core/public';
import { createAction } from '@kbn/ui-actions-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
export const ACTION_HELLO_WORLD = 'ACTION_HELLO_WORLD';
@ -18,22 +17,23 @@ interface StartServices {
openModal: OverlayStart['openModal'];
}
export const createHelloWorldAction = (getStartServices: () => Promise<StartServices>) =>
createAction({
id: ACTION_HELLO_WORLD,
type: ACTION_HELLO_WORLD,
getDisplayName: () => 'Hello World!',
execute: async () => {
const { openModal } = await getStartServices();
const overlay = openModal(
toMountPoint(
<EuiModalBody>
<EuiText data-test-subj="helloWorldActionText">Hello world!</EuiText>
<EuiButton data-test-subj="closeModal" onClick={() => overlay.close()}>
Close
</EuiButton>
</EuiModalBody>
)
);
},
});
export const createHelloWorldActionDefinition = (
getStartServices: () => Promise<StartServices>
) => ({
id: ACTION_HELLO_WORLD,
type: ACTION_HELLO_WORLD,
getDisplayName: () => 'Hello World!',
execute: async () => {
const { openModal } = await getStartServices();
const overlay = openModal(
toMountPoint(
<EuiModalBody>
<EuiText data-test-subj="helloWorldActionText">Hello world!</EuiText>
<EuiButton data-test-subj="closeModal" onClick={() => overlay.close()}>
Close
</EuiButton>
</EuiModalBody>
)
);
},
});

View file

@ -8,7 +8,7 @@
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { createHelloWorldAction } from './hello_world_action';
import { createHelloWorldActionDefinition } from './hello_world_action';
import { helloWorldTrigger } from './hello_world_trigger';
export interface UiActionExamplesSetupDependencies {
@ -29,7 +29,7 @@ export class UiActionExamplesPlugin
) {
uiActions.registerTrigger(helloWorldTrigger);
const helloWorldAction = createHelloWorldAction(async () => ({
const helloWorldAction = createHelloWorldActionDefinition(async () => ({
openModal: (await core.getStartServices())[0].overlays.openModal,
}));

View file

@ -10,7 +10,7 @@ import React from 'react';
import { EditPanelAction, isFilterableEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public';
import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import type { ApplicationStart } from '@kbn/core/public';
@ -46,7 +46,7 @@ export class FiltersNotificationAction implements Action<FiltersNotificationActi
} = pluginServices.getServices());
}
private FilterIconButton = ({ context }: { context: FiltersNotificationActionContext }) => {
public readonly MenuItem = ({ context }: { context: FiltersNotificationActionContext }) => {
const { embeddable } = context;
const editPanelAction = new EditPanelAction(
@ -76,8 +76,6 @@ export class FiltersNotificationAction implements Action<FiltersNotificationActi
);
};
public readonly MenuItem = reactToUiComponent(this.FilterIconButton);
public getDisplayName({ embeddable }: FiltersNotificationActionContext) {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
throw new IncompatibleActionError();

View file

@ -15,7 +15,7 @@ import {
isReferenceOrValueEmbeddable,
} from '@kbn/embeddable-plugin/public';
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { pluginServices } from '../services/plugin_services';
import { UnlinkFromLibraryAction } from './unlink_from_library_action';
@ -47,11 +47,7 @@ export class LibraryNotificationAction implements Action<LibraryNotificationActi
private icon = 'folderCheck';
private LibraryNotification: React.FC<{ context: LibraryNotificationActionContext }> = ({
context,
}: {
context: LibraryNotificationActionContext;
}) => {
public readonly MenuItem = ({ context }: { context: LibraryNotificationActionContext }) => {
const { embeddable } = context;
return (
<KibanaThemeProvider theme$={this.theme$}>
@ -66,8 +62,6 @@ export class LibraryNotificationAction implements Action<LibraryNotificationActi
);
};
public readonly MenuItem = reactToUiComponent(this.LibraryNotification);
public getDisplayName({ embeddable }: LibraryNotificationActionContext) {
if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) {
throw new IncompatibleActionError();

View file

@ -27,7 +27,7 @@ import {
} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables';
import { applicationServiceMock, coreMock } from '@kbn/core/public/mocks';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { createEditModeAction } from '@kbn/embeddable-plugin/public/lib/test_samples';
import { createEditModeActionDefinition } from '@kbn/embeddable-plugin/public/lib/test_samples';
import { DashboardContainer } from './dashboard_container';
import { getSampleDashboardInput, getSampleDashboardPanel } from '../../mocks';
@ -201,7 +201,7 @@ test('searchSessionId propagates to children', async () => {
test('DashboardContainer in edit mode shows edit mode actions', async () => {
const uiActionsSetup = uiActionsPluginMock.createSetupContract();
const editModeAction = createEditModeAction();
const editModeAction = createEditModeActionDefinition();
uiActionsSetup.registerAction(editModeAction);
uiActionsSetup.addTriggerAction(CONTEXT_MENU_TRIGGER, editModeAction);

View file

@ -7,7 +7,7 @@
*/
import { Datatable } from '@kbn/expressions-plugin/public';
import { Action, createAction, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { UiActionsActionDefinition, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { APPLY_FILTER_TRIGGER } from '../triggers';
import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select';
@ -25,10 +25,10 @@ export interface SelectRangeActionContext {
export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE';
export function createSelectRangeAction(
export function createSelectRangeActionDefinition(
getStartServices: () => { uiActions: UiActionsStart }
): Action {
return createAction({
): UiActionsActionDefinition<SelectRangeActionContext> {
return {
type: ACTION_SELECT_RANGE,
id: ACTION_SELECT_RANGE,
shouldAutoExecute: async () => true,
@ -47,5 +47,5 @@ export function createSelectRangeAction(
console.warn(`Error [ACTION_SELECT_RANGE]: can\'t extract filters from action context`);
}
},
});
};
}

View file

@ -8,7 +8,7 @@
import type { Filter } from '@kbn/es-query';
import { Datatable } from '@kbn/expressions-plugin/public';
import { Action, createAction, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { UiActionsActionDefinition, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { APPLY_FILTER_TRIGGER } from '../triggers';
import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click';
@ -31,10 +31,10 @@ export interface ValueClickContext {
};
}
export function createValueClickAction(
export function createValueClickActionDefinition(
getStartServices: () => { uiActions: UiActionsStart }
): Action {
return createAction({
): UiActionsActionDefinition<ValueClickContext> {
return {
type: ACTION_VALUE_CLICK,
id: ACTION_VALUE_CLICK,
shouldAutoExecute: async () => true,
@ -59,5 +59,5 @@ export function createValueClickAction(
);
}
},
});
};
}

View file

@ -34,8 +34,8 @@ import {
import {
createFiltersFromValueClickAction,
createFiltersFromRangeSelectAction,
createValueClickAction,
createSelectRangeAction,
createValueClickActionDefinition,
createSelectRangeActionDefinition,
} from './actions';
import { applyFilterTrigger } from './triggers';
import { getTableViewDescription } from './utils/table_inspector_view';
@ -142,14 +142,14 @@ export class DataPublicPlugin
uiActions.addTriggerAction(
'SELECT_RANGE_TRIGGER',
createSelectRangeAction(() => ({
createSelectRangeActionDefinition(() => ({
uiActions,
}))
);
uiActions.addTriggerAction(
'VALUE_CLICK_TRIGGER',
createValueClickAction(() => ({
createValueClickActionDefinition(() => ({
uiActions,
}))
);

View file

@ -13,12 +13,12 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { I18nProvider } from '@kbn/i18n-react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
import { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { Action, UiActionsStart, ActionInternal } from '@kbn/ui-actions-plugin/public';
import { Trigger, ViewMode } from '../types';
import { isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
import {
createEditModeAction,
createEditModeActionDefinition,
ContactCardEmbeddable,
ContactCardEmbeddableInput,
ContactCardEmbeddableOutput,
@ -38,7 +38,7 @@ const triggerRegistry = new Map<string, Trigger>();
const { setup, doStart } = embeddablePluginMock.createInstance();
const editModeAction = createEditModeAction();
const editModeAction = createEditModeActionDefinition();
const trigger: Trigger = {
id: CONTEXT_MENU_TRIGGER,
};
@ -50,7 +50,7 @@ const embeddableReactFactory = new ContactCardEmbeddableReactFactory(
const applicationMock = applicationServiceMock.createStartContract();
const theme = themeServiceMock.createStartContract();
actionRegistry.set(editModeAction.id, editModeAction);
actionRegistry.set(editModeAction.id, new ActionInternal(editModeAction));
triggerRegistry.set(trigger.id, trigger);
setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory);
setup.registerEmbeddableFactory(embeddableReactFactory.type, embeddableReactFactory);

View file

@ -163,10 +163,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
if (this.props.showBadges === false) {
return;
}
let badges =
(await this.props.getActions?.(PANEL_BADGE_TRIGGER, {
type BadgeAction = Action<
EmbeddableContext<IEmbeddable<EmbeddableInput, EmbeddableOutput, any>>
>;
let badges: BadgeAction[] =
((await this.props.getActions?.(PANEL_BADGE_TRIGGER, {
embeddable: this.props.embeddable,
})) ?? [];
})) as BadgeAction[]) ?? [];
const { disabledActions } = this.props.embeddable.getInput();
if (disabledActions) {
@ -187,10 +192,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
if (this.props.showNotifications === false) {
return;
}
let notifications =
(await this.props.getActions?.(PANEL_NOTIFICATION_TRIGGER, {
type NotificationAction = Action<
EmbeddableContext<IEmbeddable<EmbeddableInput, EmbeddableOutput, any>>
>;
let notifications: NotificationAction[] =
((await this.props.getActions?.(PANEL_NOTIFICATION_TRIGGER, {
embeddable: this.props.embeddable,
})) ?? [];
})) as NotificationAction[]) ?? [];
const { disabledActions } = this.props.embeddable.getInput();
if (disabledActions) {

View file

@ -19,7 +19,6 @@ import {
import classNames from 'classnames';
import React from 'react';
import { Action } from '@kbn/ui-actions-plugin/public';
import { uiToReactComponent } from '@kbn/kibana-react-plugin/public';
import { PanelOptionsMenu } from './panel_options_menu';
import { IEmbeddable } from '../../embeddables';
import { EmbeddableContext, panelBadgeTrigger, panelNotificationTrigger } from '../../triggers';
@ -65,7 +64,7 @@ function renderNotifications(
const context = { embeddable };
let badge = notification.MenuItem ? (
React.createElement(uiToReactComponent(notification.MenuItem), {
React.createElement(notification.MenuItem, {
key: notification.id,
context: {
embeddable,

View file

@ -6,19 +6,19 @@
* Side Public License, v 1.
*/
import { createAction } from '../../ui_actions';
import { ViewMode } from '../../types';
import { IEmbeddable } from '../..';
import { UiActionsActionDefinition } from '../../ui_actions';
export const EDIT_MODE_ACTION = 'EDIT_MODE_ACTION';
export function createEditModeAction() {
return createAction({
export function createEditModeActionDefinition(): UiActionsActionDefinition {
return {
id: EDIT_MODE_ACTION,
type: EDIT_MODE_ACTION,
getDisplayName: () => 'I only show up in edit mode',
isCompatible: async (context: { embeddable: IEmbeddable }) =>
context.embeddable.getInput().viewMode === ViewMode.EDIT,
execute: async () => {},
});
};
}

View file

@ -1,10 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './react_to_ui_component';
export * from './ui_to_react_component';

View file

@ -1,75 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as React from 'react';
import { reactToUiComponent } from './react_to_ui_component';
const ReactComp: React.FC<{ cnt?: number }> = ({ cnt = 0 }) => {
return <div>cnt: {cnt}</div>;
};
describe('reactToUiComponent', () => {
test('can render UI component', () => {
const UiComp = reactToUiComponent(ReactComp);
const div = document.createElement('div');
const instance = UiComp();
instance.render(div, {});
expect(div.innerHTML).toBe('<div>cnt: 0</div>');
});
test('can pass in props', async () => {
const UiComp = reactToUiComponent(ReactComp);
const div = document.createElement('div');
const instance = UiComp();
instance.render(div, { cnt: 5 });
expect(div.innerHTML).toBe('<div>cnt: 5</div>');
});
test('can re-render multiple times', async () => {
const UiComp = reactToUiComponent(ReactComp);
const div = document.createElement('div');
const instance = UiComp();
instance.render(div, { cnt: 1 });
expect(div.innerHTML).toBe('<div>cnt: 1</div>');
instance.render(div, { cnt: 2 });
expect(div.innerHTML).toBe('<div>cnt: 2</div>');
});
test('renders React component only when .render() method is called', () => {
let renderCnt = 0;
const MyReactComp: React.FC<{ cnt?: number }> = ({ cnt = 0 }) => {
renderCnt++;
return <div>cnt: {cnt}</div>;
};
const UiComp = reactToUiComponent(MyReactComp);
const instance = UiComp();
const div = document.createElement('div');
expect(renderCnt).toBe(0);
instance.render(div, { cnt: 1 });
expect(renderCnt).toBe(1);
instance.render(div, { cnt: 2 });
expect(renderCnt).toBe(2);
instance.render(div, { cnt: 3 });
expect(renderCnt).toBe(3);
});
});

View file

@ -1,38 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ComponentType, createElement as h } from 'react';
import { render as renderReact, unmountComponentAtNode } from 'react-dom';
import { UiComponent, UiComponentInstance } from '@kbn/kibana-utils-plugin/public';
/**
* Transform a React component into a `UiComponent`.
*
* @param ReactComp A React component.
*/
export const reactToUiComponent =
<Props extends object>(ReactComp: ComponentType<Props>): UiComponent<Props> =>
() => {
let lastEl: HTMLElement | undefined;
const render: UiComponentInstance<Props>['render'] = (el, props) => {
lastEl = el;
renderReact(h(ReactComp, props), el);
};
const unmount: UiComponentInstance<Props>['unmount'] = () => {
if (lastEl) unmountComponentAtNode(lastEl);
};
const comp: UiComponentInstance<Props> = {
render,
unmount,
};
return comp;
};

View file

@ -1,142 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UiComponent } from '@kbn/kibana-utils-plugin/public';
import { uiToReactComponent } from './ui_to_react_component';
import { reactToUiComponent } from './react_to_ui_component';
const UiComp: UiComponent<{ cnt?: number }> = () => ({
render: (el, { cnt = 0 }) => {
// eslint-disable-next-line no-unsanitized/property
el.innerHTML = `cnt: ${cnt}`;
},
});
describe('uiToReactComponent', () => {
test('can render React component', () => {
const ReactComp = uiToReactComponent(UiComp);
const div = document.createElement('div');
ReactDOM.render(<ReactComp />, div);
expect(div.innerHTML).toBe('<div>cnt: 0</div>');
});
test('can pass in props', async () => {
const ReactComp = uiToReactComponent(UiComp);
const div = document.createElement('div');
ReactDOM.render(<ReactComp cnt={5} />, div);
expect(div.innerHTML).toBe('<div>cnt: 5</div>');
});
test('re-renders when React component is re-rendered', async () => {
const ReactComp = uiToReactComponent(UiComp);
const div = document.createElement('div');
ReactDOM.render(<ReactComp cnt={1} />, div);
expect(div.innerHTML).toBe('<div>cnt: 1</div>');
ReactDOM.render(<ReactComp cnt={2} />, div);
expect(div.innerHTML).toBe('<div>cnt: 2</div>');
});
test('does not crash if .unmount() not provided', () => {
const UiComp2: UiComponent<{ cnt?: number }> = () => ({
render: (el, { cnt = 0 }) => {
// eslint-disable-next-line no-unsanitized/property
el.innerHTML = `cnt: ${cnt}`;
},
});
const ReactComp = uiToReactComponent(UiComp2);
const div = document.createElement('div');
ReactDOM.render(<ReactComp cnt={1} />, div);
ReactDOM.unmountComponentAtNode(div);
expect(div.innerHTML).toBe('');
});
test('calls .unmount() method once when component un-mounts', () => {
const unmount = jest.fn();
const UiComp2: UiComponent<{ cnt?: number }> = () => ({
render: (el, { cnt = 0 }) => {
// eslint-disable-next-line no-unsanitized/property
el.innerHTML = `cnt: ${cnt}`;
},
unmount,
});
const ReactComp = uiToReactComponent(UiComp2);
const div = document.createElement('div');
expect(unmount).toHaveBeenCalledTimes(0);
ReactDOM.render(<ReactComp cnt={1} />, div);
expect(unmount).toHaveBeenCalledTimes(0);
ReactDOM.unmountComponentAtNode(div);
expect(unmount).toHaveBeenCalledTimes(1);
});
test('calls .render() method only once when components mounts, and once on every re-render', () => {
const render = jest.fn((el, { cnt = 0 }) => {
// eslint-disable-next-line no-unsanitized/property
el.innerHTML = `cnt: ${cnt}`;
});
const UiComp2: UiComponent<{ cnt?: number }> = () => ({
render,
});
const ReactComp = uiToReactComponent(UiComp2);
const div = document.createElement('div');
expect(render).toHaveBeenCalledTimes(0);
ReactDOM.render(<ReactComp cnt={1} />, div);
expect(render).toHaveBeenCalledTimes(1);
ReactDOM.render(<ReactComp cnt={2} />, div);
expect(render).toHaveBeenCalledTimes(2);
ReactDOM.render(<ReactComp cnt={3} />, div);
expect(render).toHaveBeenCalledTimes(3);
});
test('can specify wrapper element', async () => {
const ReactComp = uiToReactComponent(UiComp, 'span');
const div = document.createElement('div');
ReactDOM.render(<ReactComp cnt={5} />, div);
expect(div.innerHTML).toBe('<span>cnt: 5</span>');
});
});
test('can adapt component many times', () => {
const ReactComp = uiToReactComponent(
reactToUiComponent(uiToReactComponent(reactToUiComponent(uiToReactComponent(UiComp))))
);
const div = document.createElement('div');
ReactDOM.render(<ReactComp />, div);
expect(div.textContent).toBe('cnt: 0');
ReactDOM.render(<ReactComp cnt={123} />, div);
expect(div.textContent).toBe('cnt: 123');
});

View file

@ -1,36 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { FC, createElement as h, useRef, useLayoutEffect, useMemo } from 'react';
import { UiComponent, UiComponentInstance } from '@kbn/kibana-utils-plugin/public';
/**
* Transforms `UiComponent` into a React component.
*/
export const uiToReactComponent =
<Props extends object>(Comp: UiComponent<Props>, as: string = 'div'): FC<Props> =>
(props) => {
const ref = useRef<HTMLDivElement>();
const comp = useMemo<UiComponentInstance<Props>>(() => Comp(), [Comp]);
useLayoutEffect(() => {
if (!ref.current) return;
comp.render(ref.current, props);
});
useLayoutEffect(() => {
if (!comp.unmount) return;
return () => {
if (comp.unmount) comp.unmount();
};
}, [comp]);
return h(as, {
ref,
});
};

View file

@ -75,8 +75,6 @@ export { createNotifications } from './notifications';
/** @deprecated use `Markdown` from `@kbn/shared-ux-markdown` */
export { Markdown, MarkdownSimple } from './markdown';
export { reactToUiComponent, uiToReactComponent } from './adapters';
export { toMountPoint, MountPointPortal } from './util';
export type { ToMountPointOptions } from './util';

View file

@ -5,7 +5,6 @@
},
"include": [".storybook/**/*", "common/**/*", "public/**/*", "../../../typings/**/*"],
"kbn_references": [
"@kbn/kibana-utils-plugin",
"@kbn/storybook",
"@kbn/ui-theme",
"@kbn/core",

View file

@ -8,7 +8,6 @@
export { Defer, defer } from './defer';
export { fieldWildcardMatcher, fieldWildcardFilter } from './field_wildcard';
export type { UiComponent, UiComponentInstance } from './ui';
export { of } from './of';
export type {
BaseState,

View file

@ -1,9 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type { UiComponent, UiComponentInstance } from './ui_component';

View file

@ -1,40 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* @public
*/
export interface UiComponentInstance<Props extends object = object> {
/**
* Call this method on initial render and on all subsequent updates.
*
* @param el DOM element.
* @param props Component props, same as props in React.
*/
render(el: HTMLElement, props: Props): void;
/**
* Un-mount UI component. Call it to remove view from DOM. Implementers of this
* interface should clear DOM from this UI component and destroy any internal state.
*/
unmount?(): void;
}
/**
* In many places in Kibana we want to be agnostic to frontend view library,
* i.e. instead of exposing React-specific APIs we want to expose APIs that
* are orthogonal to any rendering library. This interface represents such UI
* components. UI component receives a DOM element and `props` through `render()`
* method, the `render()` method can be called many times.
*
* Although Kibana aims to be library agnostic, Kibana itself is written in React,
* thus here we define `UiComponent` which is an abstract unit of UI that can be
* implemented in any framework, but it maps easily to React components, i.e.
* `UiComponent<Props>` is like `React.ComponentType<Props>`.
*/
export type UiComponent<Props extends object = object> = () => UiComponentInstance<Props>;

View file

@ -9,7 +9,7 @@
import { PluginInitializerContext } from '@kbn/core/public';
import { KibanaUtilsPublicPlugin } from './plugin';
export type { Get, Set, UiComponent, UiComponentInstance } from '../common';
export type { Get, Set } from '../common';
export {
AbortError,
abortSignalToPromise,

View file

@ -7,7 +7,7 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
import { UiComponent } from '../../common/ui/ui_component';
import type { FC } from 'react';
/**
* Represents something that can be configured by user using UI.
@ -27,9 +27,9 @@ export interface Configurable<
readonly isConfigValid: (config: Config, context: Context) => boolean;
/**
* `UiComponent` to be rendered when collecting configuration for this item.
* Component to be rendered when collecting configuration for this item.
*/
readonly CollectConfig: UiComponent<CollectConfigProps<Config, Context>>;
readonly CollectConfig: FC<CollectConfigProps<Config, Context>>;
}
/**

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import { UiComponent } from '@kbn/kibana-utils-plugin/public';
import { Presentable } from '../util/presentable';
import { Trigger } from '../triggers';
import type { Presentable } from '../util/presentable';
import type { Trigger } from '../triggers';
/**
* During action execution we can provide additional information,
@ -68,12 +67,6 @@ export interface Action<Context extends object = object>
*/
getDisplayName(context: ActionExecutionContext<Context>): string;
/**
* `UiComponent` to render when displaying this action as a context menu item.
* If not provided, `getDisplayName` will be used instead.
*/
MenuItem?: UiComponent<ActionMenuItemProps<Context>>;
/**
* Returns a promise that resolves to true if this action is compatible given the context,
* otherwise resolves to false.

View file

@ -6,69 +6,64 @@
* Side Public License, v 1.
*/
// @ts-ignore
import React from 'react';
import type { UiComponent } from '@kbn/kibana-utils-plugin/public';
import { uiToReactComponent } from '@kbn/kibana-react-plugin/public';
import { Action, ActionContext as Context, ActionDefinition, ActionMenuItemProps } from './action';
import * as React from 'react';
import { Action, ActionDefinition, ActionMenuItemProps } from './action';
import { Presentable, PresentableGrouping } from '../util/presentable';
/**
* @internal
*/
export class ActionInternal<A extends ActionDefinition = ActionDefinition>
implements Action<Context<A>>, Presentable<Context<A>>
export class ActionInternal<Context extends object = object>
implements Action<Context>, Presentable<Context>
{
public readonly id: string;
public readonly type: string;
public readonly order: number;
public readonly MenuItem?: UiComponent<ActionMenuItemProps<Context<A>>>;
public readonly ReactMenuItem?: React.FC<ActionMenuItemProps<Context<A>>>;
public readonly grouping?: PresentableGrouping<Context<A>>;
public readonly MenuItem?: React.FC<ActionMenuItemProps<any>>;
public readonly grouping?: PresentableGrouping<Context>;
public readonly showNotification?: boolean;
public readonly disabled?: boolean;
constructor(public readonly definition: A) {
constructor(public readonly definition: ActionDefinition<Context>) {
this.id = this.definition.id;
this.type = this.definition.type || '';
this.order = this.definition.order || 0;
this.MenuItem = this.definition.MenuItem;
this.ReactMenuItem = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined;
this.grouping = this.definition.grouping;
this.showNotification = this.definition.showNotification;
this.disabled = this.definition.disabled;
}
public execute(context: Context<A>) {
public execute(context: Context) {
return this.definition.execute(context);
}
public getIconType(context: Context<A>): string | undefined {
public getIconType(context: Context): string | undefined {
if (!this.definition.getIconType) return undefined;
return this.definition.getIconType(context);
}
public getDisplayName(context: Context<A>): string {
public getDisplayName(context: Context): string {
if (!this.definition.getDisplayName) return `Action: ${this.id}`;
return this.definition.getDisplayName(context);
}
public getDisplayNameTooltip(context: Context<A>): string {
public getDisplayNameTooltip(context: Context): string {
if (!this.definition.getDisplayNameTooltip) return '';
return this.definition.getDisplayNameTooltip(context);
}
public async isCompatible(context: Context<A>): Promise<boolean> {
public async isCompatible(context: Context): Promise<boolean> {
if (!this.definition.isCompatible) return true;
return await this.definition.isCompatible(context);
}
public async getHref(context: Context<A>): Promise<string | undefined> {
public async getHref(context: Context): Promise<string | undefined> {
if (!this.definition.getHref) return undefined;
return await this.definition.getHref(context);
}
public async shouldAutoExecute(context: Context<A>): Promise<boolean> {
public async shouldAutoExecute(context: Context): Promise<boolean> {
if (!this.definition.shouldAutoExecute) return false;
return this.definition.shouldAutoExecute(context);
}

View file

@ -10,8 +10,8 @@ import * as React from 'react';
import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import type { Trigger } from '../triggers';
import type { Action, ActionDefinition, ActionExecutionContext, ActionInternal } from '../actions';
import { Trigger } from '../triggers';
import type { Action, ActionExecutionContext, ActionInternal } from '../actions';
export const defaultTitle = i18n.translate('uiActions.actionPanel.title', {
defaultMessage: 'Options',
@ -22,7 +22,7 @@ export const txtMore = i18n.translate('uiActions.actionPanel.more', {
});
interface ActionWithContext<Context extends object = object> {
action: Action<Context> | ActionInternal<ActionDefinition<Context>>;
action: Action<Context> | ActionInternal<Context>;
context: Context;
/**
@ -43,11 +43,7 @@ type PanelDescriptor = EuiContextMenuPanelDescriptor & {
};
const onClick =
(
action: Action | ActionInternal<ActionDefinition>,
context: ActionExecutionContext<object>,
close: () => void
) =>
(action: Action | ActionInternal, context: ActionExecutionContext<object>, close: () => void) =>
(event: React.MouseEvent) => {
if (event.currentTarget instanceof HTMLAnchorElement) {
// from react-router's <Link/>
@ -166,10 +162,8 @@ export async function buildContextMenuForActions({
}
}
panels[parentPanel || 'mainMenu'].items!.push({
name: (action as ActionInternal<ActionDefinition>).ReactMenuItem
? React.createElement((action as ActionInternal<ActionDefinition>).ReactMenuItem!, {
context,
})
name: action.MenuItem
? React.createElement(action.MenuItem, { context })
: action.getDisplayName(context),
icon: action.getIconType(context),
toolTipContent: action.getDisplayNameTooltip ? action.getDisplayNameTooltip(context) : '',

View file

@ -17,7 +17,7 @@ export type { UiActionsSetup, UiActionsStart } from './plugin';
export type { UiActionsServiceParams } from './service';
export { UiActionsService } from './service';
export type { Action, ActionDefinition as UiActionsActionDefinition } from './actions';
export { createAction, IncompatibleActionError } from './actions';
export { ActionInternal, createAction, IncompatibleActionError } from './actions';
export { buildContextMenuForActions } from './context_menu';
export type {
Presentable as UiActionsPresentable,

View file

@ -7,7 +7,7 @@
*/
import { UiActionsService } from './ui_actions_service';
import { Action, ActionDefinition, ActionInternal, createAction } from '../actions';
import { ActionDefinition, ActionInternal } from '../actions';
import { createHelloWorldAction } from '../tests/test_samples';
import { TriggerRegistry, ActionRegistry } from '../types';
import { Trigger } from '../triggers';
@ -17,7 +17,7 @@ const FOO_TRIGGER = 'FOO_TRIGGER';
const BAR_TRIGGER = 'BAR_TRIGGER';
const MY_TRIGGER = 'MY_TRIGGER';
const testAction1: Action = {
const testAction1: ActionDefinition = {
id: 'action1',
order: 1,
type: 'type1',
@ -27,7 +27,7 @@ const testAction1: Action = {
isCompatible: async () => true,
};
const testAction2: Action = {
const testAction2: ActionDefinition = {
id: 'action2',
order: 2,
type: 'type2',
@ -108,7 +108,7 @@ describe('UiActionsService', () => {
});
describe('.getTriggerActions()', () => {
const action1: Action = {
const action1: ActionDefinition = {
id: 'action1',
order: 1,
type: 'type1',
@ -117,7 +117,7 @@ describe('UiActionsService', () => {
getIconType: () => '',
isCompatible: async () => true,
};
const action2: Action = {
const action2: ActionDefinition = {
id: 'action2',
order: 2,
type: 'type2',
@ -194,12 +194,12 @@ describe('UiActionsService', () => {
test('filters out actions not applicable based on the context', async () => {
const service = new UiActionsService();
const action = createAction({
const action = {
id: 'test',
type: 'test',
isCompatible: ({ accept }: { accept: boolean }) => Promise.resolve(accept),
execute: () => Promise.resolve(),
});
};
service.registerAction(action);

View file

@ -7,7 +7,7 @@
*/
import { TriggerRegistry, ActionRegistry, TriggerToActionsRegistry } from '../types';
import { ActionInternal, Action, ActionDefinition, ActionContext } from '../actions';
import { ActionInternal, Action, ActionDefinition } from '../actions';
import { Trigger } from '../triggers/trigger';
import { TriggerInternal } from '../triggers/trigger_internal';
import { TriggerContract } from '../triggers/trigger_contract';
@ -60,16 +60,16 @@ export class UiActionsService {
return trigger.contract;
};
public readonly registerAction = <A extends ActionDefinition>(
definition: A
): Action<ActionContext<A>> => {
public readonly registerAction = <Context extends object>(
definition: ActionDefinition<Context>
): Action<Context> => {
if (this.actions.has(definition.id)) {
throw new Error(`Action [action.id = ${definition.id}] already registered.`);
}
const action = new ActionInternal(definition);
this.actions.set(action.id, action);
this.actions.set(action.id, action as unknown as ActionInternal<object>);
return action;
};
@ -123,22 +123,17 @@ 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.
*/
public readonly addTriggerAction = (
triggerId: string,
action: ActionDefinition // TODO: remove `Action` https://github.com/elastic/kibana/issues/74501
): void => {
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 = <T extends ActionDefinition>(
id: string
): Action<ActionContext<T>> => {
public readonly getAction = (id: string): Action => {
if (!this.actions.has(id)) {
throw new Error(`Action [action.id = ${id}] not registered.`);
}
return this.actions.get(id) as ActionInternal<T>;
return this.actions.get(id)! as Action;
};
public readonly getTriggerActions = (triggerId: string): Action[] => {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Action, createAction } from '../actions';
import { ActionDefinition } from '../actions';
import { openContextMenu } from '../context_menu';
import { uiActionsPluginMock } from '../mocks';
import { Trigger } from '../triggers';
@ -23,14 +23,14 @@ function createTestAction<C extends object>(
type: string,
checkCompatibility: (context: C) => boolean,
autoExecutable = false
): Action<object> {
return createAction({
): ActionDefinition {
return {
type,
id: type,
isCompatible: (context: C) => Promise.resolve(checkCompatibility(context)),
execute: (context) => executeFn(context),
shouldAutoExecute: () => Promise.resolve(autoExecutable),
});
};
}
let uiActions: ReturnType<typeof uiActionsPluginMock.createPlugin>;

View file

@ -6,19 +6,22 @@
* Side Public License, v 1.
*/
import { ActionInternal, Action } from '../actions';
import { ActionInternal, ActionDefinition } from '../actions';
import { uiActionsPluginMock } from '../mocks';
const action1: Action = {
const action1: ActionDefinition = {
id: 'action1',
order: 1,
type: 'type1',
} as unknown as Action;
const action2: Action = {
execute: async () => {},
};
const action2: ActionDefinition = {
id: 'action2',
order: 2,
type: 'type2',
} as unknown as Action;
execute: async () => {},
};
test('returns actions set on trigger', () => {
const { setup, doStart } = uiActionsPluginMock.createPlugin();

View file

@ -8,26 +8,26 @@
import { uiActionsPluginMock } from '../mocks';
import { createHelloWorldAction } from './test_samples';
import { Action, createAction } from '../actions';
import { ActionDefinition } from '../actions';
import { Trigger } from '../triggers';
import { OverlayStart } from '@kbn/core/public';
let action: Action<{ name: string }>;
let action: ActionDefinition<{ name: string }>;
let uiActions: ReturnType<typeof uiActionsPluginMock.createPlugin>;
beforeEach(() => {
uiActions = uiActionsPluginMock.createPlugin();
action = createAction({
action = {
id: 'test',
type: 'test',
execute: () => Promise.resolve(),
});
};
uiActions.setup.registerAction(action);
uiActions.setup.registerAction(action as ActionDefinition);
uiActions.setup.registerTrigger({
id: 'trigger',
title: 'trigger',
});
uiActions.setup.addTriggerAction('trigger', action);
uiActions.setup.addTriggerAction('trigger', action as ActionDefinition);
});
test('can register action', async () => {
@ -59,14 +59,14 @@ test('getTriggerCompatibleActions returns attached actions', async () => {
test('filters out actions not applicable based on the context', async () => {
const { setup, doStart } = uiActions;
const action1 = createAction({
const action1 = {
id: 'test1',
type: 'test1',
isCompatible: async (context: { accept: boolean }) => {
return Promise.resolve(context.accept);
},
execute: () => Promise.resolve(),
});
};
const testTrigger: Trigger = {
id: 'MY-TRIGGER2',

View file

@ -9,10 +9,10 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiFlyoutBody } from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { toMountPoint, reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { createAction, Action } from '../../actions';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { ActionDefinition } from '../../actions';
const ReactMenuItem: React.FC = () => {
const MenuItem: React.FC = () => {
return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>Hello world!</EuiFlexItem>
@ -23,16 +23,14 @@ const ReactMenuItem: React.FC = () => {
);
};
const UiMenuItem = reactToUiComponent(ReactMenuItem);
export const ACTION_HELLO_WORLD = 'ACTION_HELLO_WORLD';
export function createHelloWorldAction(overlays: CoreStart['overlays']): Action {
return createAction({
export function createHelloWorldAction(overlays: CoreStart['overlays']): ActionDefinition {
return {
id: ACTION_HELLO_WORLD,
type: ACTION_HELLO_WORLD,
getIconType: () => 'lock',
MenuItem: UiMenuItem,
MenuItem,
execute: async () => {
overlays.openFlyout(
toMountPoint(<EuiFlyoutBody>Hello World, I am a hello world action!</EuiFlyoutBody>),
@ -42,5 +40,5 @@ export function createHelloWorldAction(overlays: CoreStart['overlays']): Action
}
);
},
});
};
}

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { UiComponent } from '@kbn/kibana-utils-plugin/public';
import type { FC } from 'react';
/**
* Represents something that can be displayed to user in UI.
@ -24,10 +24,10 @@ export interface Presentable<Context = unknown> {
readonly order: number;
/**
* `UiComponent` to render when displaying this entity as a context menu item.
* Component to render when displaying this entity as a context menu item.
* If not provided, `getDisplayName` will be used instead.
*/
readonly MenuItem?: UiComponent<{ context: Context }>;
readonly MenuItem?: FC<{ context: Context }>;
/**
* Optional EUI icon type that can be displayed along with the title.

View file

@ -298,11 +298,7 @@ const SelectedActionFactory: React.FC<SelectedActionFactoryProps> = ({
)}
<EuiSpacer size="m" />
<div>
<actionFactory.ReactCollectConfig
config={config}
onConfig={onConfigChange}
context={context}
/>
<actionFactory.CollectConfig config={config} onConfig={onConfigChange} context={context} />
</div>
</div>
);

View file

@ -8,7 +8,6 @@
import React, { useState } from 'react';
import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { CollectConfigProps } from '@kbn/kibana-utils-plugin/public';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import {
@ -98,7 +97,7 @@ export const dashboardDrilldownActionFactory: ActionFactoryDefinition<
if (!config.dashboardId) return false;
return true;
},
CollectConfig: reactToUiComponent(DashboardDrilldownCollectConfig),
CollectConfig: DashboardDrilldownCollectConfig,
isCompatible(context?: object): Promise<boolean> {
return Promise.resolve(true);
@ -164,7 +163,7 @@ export const urlDrilldownActionFactory: ActionFactoryDefinition<UrlDrilldownConf
if (!config.url) return false;
return true;
},
CollectConfig: reactToUiComponent(UrlDrilldownCollectConfig),
CollectConfig: UrlDrilldownCollectConfig,
order: 10,
isCompatible(context?: object): Promise<boolean> {

View file

@ -6,9 +6,10 @@
* Side Public License, v 1.
*/
import type { FC } from 'react';
import type { LicenseType } from '@kbn/licensing-plugin/public';
import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import type { PersistableStateDefinition, UiComponent } from '@kbn/kibana-utils-plugin/common';
import type { PersistableStateDefinition } from '@kbn/kibana-utils-plugin/common';
import type {
ActionFactoryDefinition,
BaseActionConfig,
@ -72,22 +73,17 @@ export interface DrilldownDefinition<
createConfig: ActionFactoryDefinition<Config, ExecutionContext, FactoryContext>['createConfig'];
/**
* `UiComponent` that collects config for this drilldown. You can create
* a React component and transform it `UiComponent` using `uiToReactComponent`
* helper from `kibana_utils` plugin.
* Component that collects config for this drilldown.
*
* ```tsx
* import React from 'react';
* import { uiToReactComponent } from 'src/plugins/kibana_utils';
* import { CollectConfigProps } from 'src/plugins/kibana_utils/public';
*
* type Props = CollectConfigProps<Config>;
*
* const ReactCollectConfig: React.FC<Props> = () => {
* export const CollectConfig: React.FC<Props> = () => {
* return <div>Collecting config...'</div>;
* };
*
* export const CollectConfig = uiToReactComponent(ReactCollectConfig);
* ```
*/
CollectConfig: ActionFactoryDefinition<Config, ExecutionContext, FactoryContext>['CollectConfig'];
@ -118,7 +114,7 @@ export interface DrilldownDefinition<
* Name of the drilldown instance displayed to the user at the moment of
* drilldown execution. Should be internationalized.
*/
readonly actionMenuItem?: UiComponent<{
readonly actionMenuItem?: FC<{
config: Omit<SerializedAction<Config>, 'factoryId'>;
context: ExecutionContext | ActionExecutionContext<ExecutionContext>;
}>;

View file

@ -42,7 +42,7 @@ export const DrilldownStateForm: React.FC<DrilldownStateFormProps> = ({ state, d
triggers={triggerPickerProps}
disabled={disabled}
>
<state.factory.ReactCollectConfig
<state.factory.CollectConfig
config={config}
onConfig={disabled ? () => {} : state.setConfig}
context={context}

View file

@ -36,11 +36,7 @@ export const CreateDrilldownForm: React.FC<CreateDrilldownFormProps> = ({ state
return (
<DrilldownForm name={name} onNameChange={state.setName} triggers={triggerPickerProps}>
<state.factory.ReactCollectConfig
config={config}
onConfig={state.setConfig}
context={context}
/>
<state.factory.CollectConfig config={config} onConfig={state.setConfig} context={context} />
</DrilldownForm>
);
};

View file

@ -46,11 +46,7 @@ export const EditDrilldownForm: React.FC<EditDrilldownFormProps> = ({ state }) =
return (
<>
<DrilldownForm name={name} onNameChange={state.setName} triggers={triggerPickerProps}>
<state.factory.ReactCollectConfig
config={config}
onConfig={state.setConfig}
context={context}
/>
<state.factory.CollectConfig config={config} onConfig={state.setConfig} context={context} />
</DrilldownForm>
<EuiSpacer size={'xl'} />
<EuiButton

View file

@ -42,7 +42,7 @@ const createDrilldownManagerState = () => {
const factory1 = new ActionFactory(
{
id: 'FACTORY1',
CollectConfig: () => ({ render: () => {} }),
CollectConfig: () => null,
supportedTriggers: () => ['TRIGGER1', 'TRIGGER2'],
isConfigValid: () => true,
createConfig: () => ({}),
@ -56,7 +56,7 @@ const createDrilldownManagerState = () => {
const factory2 = new ActionFactory(
{
id: 'FACTORY2',
CollectConfig: () => ({ render: () => {} }),
CollectConfig: () => null,
supportedTriggers: () => ['TRIGGER2', 'TRIGGER3'],
isConfigValid: () => true,
createConfig: () => ({}),
@ -70,7 +70,7 @@ const createDrilldownManagerState = () => {
const factory3 = new ActionFactory(
{
id: 'FACTORY3',
CollectConfig: () => ({ render: () => {} }),
CollectConfig: () => null,
supportedTriggers: () => ['TRIGGER_MISSING'],
isConfigValid: () => true,
createConfig: () => ({}),

View file

@ -6,12 +6,12 @@
* Side Public License, v 1.
*/
import type { UiComponent, CollectConfigProps } from '@kbn/kibana-utils-plugin/public';
import type { FC } from 'react';
import type { CollectConfigProps } from '@kbn/kibana-utils-plugin/public';
import type {
MigrateFunctionsObject,
GetMigrationFunctionObjectFn,
} from '@kbn/kibana-utils-plugin/common';
import { uiToReactComponent } from '@kbn/kibana-react-plugin/public';
import type {
UiActionsPresentable as Presentable,
ActionMenuItemProps,
@ -48,11 +48,9 @@ export class ActionFactory<
public readonly minimalLicense?: LicenseType;
public readonly licenseFeatureName?: string;
public readonly order: number;
public readonly MenuItem?: UiComponent<ActionMenuItemProps<FactoryContext>>;
public readonly ReactMenuItem?: React.FC<ActionMenuItemProps<FactoryContext>>;
public readonly MenuItem?: FC<ActionMenuItemProps<any>>;
public readonly CollectConfig: UiComponent<CollectConfigProps<Config, FactoryContext>>;
public readonly ReactCollectConfig: React.FC<CollectConfigProps<Config, FactoryContext>>;
public readonly CollectConfig: FC<CollectConfigProps<Config, FactoryContext>>;
public readonly createConfig: (context: FactoryContext) => Config;
public readonly isConfigValid: (config: Config, context: FactoryContext) => boolean;
public readonly migrations: MigrateFunctionsObject | GetMigrationFunctionObjectFn;
@ -73,9 +71,7 @@ export class ActionFactory<
this.licenseFeatureName = this.def.licenseFeatureName;
this.order = this.def.order || 0;
this.MenuItem = this.def.MenuItem;
this.ReactMenuItem = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined;
this.CollectConfig = this.def.CollectConfig;
this.ReactCollectConfig = uiToReactComponent(this.CollectConfig);
this.createConfig = this.def.createConfig;
this.isConfigValid = this.def.isConfigValid;
this.migrations = this.def.migrations || {};

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { createElement } from 'react';
import { SerializableRecord } from '@kbn/utility-types';
import { ILicense } from '@kbn/licensing-plugin/common/types';
import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public';
@ -67,7 +68,7 @@ export class UiActionsServiceEnhancements
actionFactory.id,
actionFactory as unknown as ActionFactory<
SerializableRecord,
ExecutionContext,
object,
BaseActionFactoryContext
>
);
@ -145,15 +146,7 @@ export class UiActionsServiceEnhancements
getIconType: () => euiIcon,
getDisplayName: () => serializedAction.name,
MenuItem: actionMenuItem
? () => {
const comp = actionMenuItem();
return {
render: (el, { context }) => {
comp.render(el, { context, config: serializedAction });
},
unmount: comp.unmount,
};
}
? ({ context }) => createElement(actionMenuItem, { context, config: serializedAction })
: undefined,
execute: async (context) => await execute(serializedAction.config, context),
getHref: getHref ? async (context) => getHref(serializedAction.config, context) : undefined,

View file

@ -12,13 +12,13 @@ import { ReactWrapper } from 'enzyme';
import { EuiButton, EuiPopoverFooter } from '@elastic/eui';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub';
import { ActionInternal } from '@kbn/ui-actions-plugin/public';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { FieldVisualizeButton } from './field_visualize_button';
import {
ACTION_VISUALIZE_LENS_FIELD,
VISUALIZE_FIELD_TRIGGER,
VISUALIZE_GEO_FIELD_TRIGGER,
createAction,
VisualizeFieldContext,
} from '@kbn/ui-actions-plugin/public';
import { TriggerContract } from '@kbn/ui-actions-plugin/public/triggers';
@ -26,7 +26,7 @@ import { TriggerContract } from '@kbn/ui-actions-plugin/public/triggers';
const ORIGINATING_APP = 'test';
const mockExecuteAction = jest.fn();
const uiActions = uiActionsPluginMock.createStartContract();
const visualizeAction = createAction<VisualizeFieldContext>({
const visualizeAction = new ActionInternal({
type: ACTION_VISUALIZE_LENS_FIELD,
id: ACTION_VISUALIZE_LENS_FIELD,
getDisplayName: () => 'test',
@ -37,7 +37,9 @@ const visualizeAction = createAction<VisualizeFieldContext>({
getHref: async () => '/app/test',
});
jest.spyOn(uiActions, 'getTriggerCompatibleActions').mockResolvedValue([visualizeAction]);
jest
.spyOn(uiActions, 'getTriggerCompatibleActions')
.mockResolvedValue([visualizeAction as ActionInternal<object>]);
jest.spyOn(uiActions, 'getTrigger').mockReturnValue({
id: ACTION_VISUALIZE_LENS_FIELD,
exec: mockExecuteAction,

View file

@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { ThemeServiceSetup } from '@kbn/core/public';
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { Action, createAction, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError, UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
// for cleanup esFilters need to fix the issue https://github.com/elastic/kibana/issues/131292
import { FilterManager, TimefilterContract } from '@kbn/data-plugin/public';
import type { Filter, RangeFilter } from '@kbn/es-query';
@ -36,8 +36,8 @@ export function createFilterAction(
filterManager: FilterManager,
timeFilter: TimefilterContract,
theme: ThemeServiceSetup
): Action {
return createAction({
): UiActionsActionDefinition<ApplyGlobalFilterActionContext> {
return {
type: ACTION_GLOBAL_APPLY_FILTER,
id: ACTION_GLOBAL_APPLY_FILTER,
order: 100,
@ -115,7 +115,7 @@ export function createFilterAction(
filterManager.addFilters(selectedFilters);
}
},
});
};
}
async function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Action, ActionExecutionMeta, createAction } from '@kbn/ui-actions-plugin/public';
import { ActionExecutionMeta, UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { FilterManager } from '@kbn/data-plugin/public';
export const UPDATE_FILTER_REFERENCES_ACTION = 'UPDATE_FILTER_REFERENCES_ACTION';
@ -24,8 +24,10 @@ export interface UpdateFilterReferencesActionContext extends ActionExecutionMeta
defaultDataView?: string;
}
export function createUpdateFilterReferencesAction(filterManager: FilterManager): Action {
return createAction<UpdateFilterReferencesActionContext>({
export function createUpdateFilterReferencesAction(
filterManager: FilterManager
): UiActionsActionDefinition<UpdateFilterReferencesActionContext> {
return {
type: UPDATE_FILTER_REFERENCES_ACTION,
id: UPDATE_FILTER_REFERENCES_ACTION,
execute: async ({ fromDataView, toDataView, usedDataViews, defaultDataView }) => {
@ -65,5 +67,5 @@ export function createUpdateFilterReferencesAction(filterManager: FilterManager)
);
}
},
});
};
}

View file

@ -83,15 +83,9 @@ export class UnifiedSearchPublicPlugin
},
});
uiActions.addTriggerAction(
APPLY_FILTER_TRIGGER,
uiActions.getAction(ACTION_GLOBAL_APPLY_FILTER)
);
uiActions.attachAction(APPLY_FILTER_TRIGGER, ACTION_GLOBAL_APPLY_FILTER);
uiActions.addTriggerAction(
UPDATE_FILTER_REFERENCES_TRIGGER,
uiActions.getAction(UPDATE_FILTER_REFERENCES_ACTION)
);
uiActions.attachAction(UPDATE_FILTER_REFERENCES_TRIGGER, UPDATE_FILTER_REFERENCES_ACTION);
return {
ui: {

View file

@ -10,7 +10,6 @@ import React from 'react';
import { take } from 'rxjs/operators';
import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import { TimefilterContract } from '@kbn/data-plugin/public';
import { i18n } from '@kbn/i18n';
@ -30,7 +29,7 @@ const displayName = i18n.translate('visualizations.actions.editInLens.displayNam
defaultMessage: 'Convert to Lens',
});
const ReactMenuItem: React.FC = () => {
const MenuItem: React.FC = () => {
return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>{displayName}</EuiFlexItem>
@ -45,8 +44,6 @@ const ReactMenuItem: React.FC = () => {
);
};
const UiMenuItem = reactToUiComponent(ReactMenuItem);
const isVisualizeEmbeddable = (embeddable: IEmbeddable): embeddable is VisualizeEmbeddable => {
return 'getVis' in embeddable;
};
@ -106,7 +103,7 @@ export class EditInLensAction implements Action<EditInLensContext> {
return displayName;
}
MenuItem = UiMenuItem;
MenuItem = MenuItem;
getIconType(context: ActionExecutionContext<EditInLensContext>): string | undefined {
return 'merge';

View file

@ -39,7 +39,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
},
})),
withKibana: jest.fn((comp) => comp),
reactToUiComponent: jest.fn(),
}));
jest.mock('../../services', () => ({

View file

@ -8,7 +8,6 @@
import React from 'react';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import type { SerializableRecord } from '@kbn/utility-types';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '@kbn/ui-actions-enhanced-plugin/public';
import { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public';
import { SAMPLE_APP1_CLICK_TRIGGER, SampleApp1ClickContext } from '../../triggers';
@ -20,7 +19,7 @@ export interface Config extends SerializableRecord {
type Trigger = typeof SAMPLE_APP1_CLICK_TRIGGER;
type Context = SampleApp1ClickContext;
export type CollectConfigProps = CollectConfigPropsBase<Config, { triggers: Trigger[] }>;
export type CollectConfigProps = CollectConfigPropsBase<Config, { triggers: string[] }>;
export const APP1_HELLO_WORLD_DRILLDOWN = 'APP1_HELLO_WORLD_DRILLDOWN';
@ -37,11 +36,7 @@ export class App1HelloWorldDrilldown implements Drilldown<Config, Context> {
return [SAMPLE_APP1_CLICK_TRIGGER];
}
private readonly ReactCollectConfig: React.FC<CollectConfigProps> = ({
config,
onConfig,
context,
}) => (
public readonly CollectConfig: React.FC<CollectConfigProps> = ({ config, onConfig }) => (
<EuiFormRow label="Enter your name" fullWidth>
<EuiFieldText
fullWidth
@ -51,8 +46,6 @@ export class App1HelloWorldDrilldown implements Drilldown<Config, Context> {
</EuiFormRow>
);
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly createConfig = () => ({
name: '',
});

View file

@ -7,7 +7,6 @@
import React from 'react';
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '@kbn/ui-actions-enhanced-plugin/public';
import {
ChartActionContext,
@ -37,11 +36,7 @@ export class DashboardHelloWorldDrilldown implements Drilldown<Config, ActionCon
return [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER];
}
private readonly ReactCollectConfig: React.FC<CollectConfigProps<Config>> = ({
config,
onConfig,
context,
}) => (
public readonly CollectConfig = ({ config, onConfig, context }: CollectConfigProps<Config>) => (
<EuiFormRow label="Enter your name" fullWidth>
<EuiFieldText
fullWidth
@ -51,8 +46,6 @@ export class DashboardHelloWorldDrilldown implements Drilldown<Config, ActionCon
</EuiFormRow>
);
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly createConfig = () => ({
name: '',
});

View file

@ -7,7 +7,6 @@
import React from 'react';
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '@kbn/ui-actions-enhanced-plugin/public';
import { RangeSelectContext, SELECT_RANGE_TRIGGER } from '@kbn/embeddable-plugin/public';
import { CollectConfigProps } from '@kbn/kibana-utils-plugin/public';
@ -35,10 +34,7 @@ export class DashboardHelloWorldOnlyRangeSelectDrilldown
return [SELECT_RANGE_TRIGGER];
}
private readonly ReactCollectConfig: React.FC<CollectConfigProps<Config>> = ({
config,
onConfig,
}) => (
public readonly CollectConfig = ({ config, onConfig }: CollectConfigProps<Config>) => (
<EuiFormRow label="Enter your name" fullWidth>
<EuiFieldText
fullWidth
@ -48,8 +44,6 @@ export class DashboardHelloWorldOnlyRangeSelectDrilldown
</EuiFormRow>
);
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly createConfig = () => ({
name: '',
});

View file

@ -6,7 +6,6 @@
*/
import React from 'react';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '@kbn/ui-actions-enhanced-plugin/public';
import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public';
@ -31,9 +30,7 @@ export interface Params {
export class DashboardToDiscoverDrilldown
implements Drilldown<Config, ApplyGlobalFilterActionContext>
{
constructor(protected readonly params: Params) {
this.ReactCollectConfig = (props) => <CollectConfigContainer {...props} params={this.params} />;
}
constructor(protected readonly params: Params) {}
public readonly id = SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN;
@ -47,9 +44,9 @@ export class DashboardToDiscoverDrilldown
return [APPLY_FILTER_TRIGGER];
}
private readonly ReactCollectConfig!: React.FC<CollectConfigProps>;
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly CollectConfig = (props: CollectConfigProps) => (
<CollectConfigContainer {...props} params={this.params} />
);
public readonly createConfig = () => ({
customIndexPattern: false,

View file

@ -9,17 +9,12 @@ import type { KibanaLocation } from '@kbn/share-plugin/public';
import React from 'react';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DashboardStart } from '@kbn/dashboard-plugin/public';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import {
AdvancedUiActionsStart,
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
UiActionsEnhancedDrilldownDefinition as Drilldown,
} from '@kbn/ui-actions-enhanced-plugin/public';
import {
CollectConfigProps,
StartServicesGetter,
UiComponent,
} from '@kbn/kibana-utils-plugin/public';
import { CollectConfigProps, StartServicesGetter } from '@kbn/kibana-utils-plugin/public';
import { DrilldownConfig } from '../../../../common/drilldowns/dashboard_drilldown/types';
import { CollectConfigContainer } from './components';
import { txtGoToDashboard } from './i18n';
@ -38,7 +33,7 @@ export abstract class AbstractDashboardDrilldown<Context extends object = object
{
constructor(protected readonly params: Params) {
this.ReactCollectConfig = (props) => <CollectConfigContainer {...props} params={this.params} />;
this.CollectConfig = reactToUiComponent(this.ReactCollectConfig);
this.CollectConfig = this.ReactCollectConfig;
}
public abstract readonly id: string;
@ -61,7 +56,7 @@ export abstract class AbstractDashboardDrilldown<Context extends object = object
CollectConfigProps<Config, BaseActionFactoryContext>
>;
public readonly CollectConfig: UiComponent<
public readonly CollectConfig: React.FC<
CollectConfigProps<DrilldownConfig, BaseActionFactoryContext>
>;

View file

@ -9,7 +9,7 @@ import React from 'react';
import { distinctUntilChanged, filter, map, skip, take, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { Action } from '@kbn/ui-actions-plugin/public';
import { reactToUiComponent, toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { EmbeddableContext, ViewMode, CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
import {
isEnhancedEmbeddable,
@ -43,7 +43,7 @@ export class FlyoutEditDrilldownAction implements Action<EmbeddableContext> {
return 'list';
}
MenuItem = reactToUiComponent(MenuItem);
public readonly MenuItem = MenuItem as any;
public async isCompatible({ embeddable }: EmbeddableContext) {
if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false;

View file

@ -14,18 +14,15 @@ import { EnhancedEmbeddable } from '@kbn/embeddable-enhanced-plugin/public';
test('<MenuItem/>', () => {
const state = createStateContainer<{ events: object[] }>({ events: [] });
const { getByText, queryByText } = render(
<MenuItem
context={{
embeddable: {
enhancements: {
dynamicActions: { state } as unknown as DynamicActionManager,
},
} as unknown as EnhancedEmbeddable,
trigger: {} as any,
}}
/>
);
const context = {
embeddable: {
enhancements: {
dynamicActions: { state } as unknown as DynamicActionManager,
},
} as unknown as EnhancedEmbeddable,
trigger: {},
};
const { getByText, queryByText } = render(<MenuItem context={context} />);
expect(getByText(/manage drilldowns/i)).toBeInTheDocument();
expect(queryByText('0')).not.toBeInTheDocument();

View file

@ -9,12 +9,9 @@ import React from 'react';
import { EuiNotificationBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useContainerState } from '@kbn/kibana-utils-plugin/public';
import { EnhancedEmbeddableContext } from '@kbn/embeddable-enhanced-plugin/public';
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import { txtDisplayName } from './i18n';
export const MenuItem: React.FC<{ context: ActionExecutionContext<EnhancedEmbeddableContext> }> = ({
context,
}) => {
export const MenuItem = ({ context }: { context: EnhancedEmbeddableContext }) => {
const { events } = useContainerState(context.embeddable.enhancements.dynamicActions.state);
const count = events.length;

View file

@ -18,15 +18,8 @@ import {
import { IMAGE_CLICK_TRIGGER } from '@kbn/image-embeddable-plugin/public';
import { ActionExecutionContext, ROW_CLICK_TRIGGER } from '@kbn/ui-actions-plugin/public';
import type { Query, Filter, TimeRange } from '@kbn/es-query';
import type {
CollectConfigProps as CollectConfigPropsBase,
UiComponent,
} from '@kbn/kibana-utils-plugin/public';
import {
reactToUiComponent,
UrlTemplateEditorVariable,
KibanaContextProvider,
} from '@kbn/kibana-react-plugin/public';
import type { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public';
import { UrlTemplateEditorVariable, KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import {
UiActionsEnhancedDrilldownDefinition as Drilldown,
UrlDrilldownGlobalScope,
@ -88,10 +81,10 @@ export class UrlDrilldown implements Drilldown<Config, ActionContext, ActionFact
public readonly getDisplayName = () => txtUrlDrilldownDisplayName;
public readonly actionMenuItem: UiComponent<{
public readonly actionMenuItem: React.FC<{
config: Omit<SerializedAction<UrlDrilldownConfig>, 'factoryId'>;
context: ActionContext | ActionExecutionContext<ActionContext>;
}> = reactToUiComponent(({ config, context }) => {
}> = ({ config, context }) => {
const [title, setTitle] = React.useState(config.name);
React.useEffect(() => {
let unmounted = false;
@ -107,7 +100,7 @@ export class UrlDrilldown implements Drilldown<Config, ActionContext, ActionFact
};
});
return <>{title}</>;
});
};
public readonly euiIcon = 'link';
@ -120,12 +113,7 @@ export class UrlDrilldown implements Drilldown<Config, ActionContext, ActionFact
IMAGE_CLICK_TRIGGER,
];
}
private readonly ReactCollectConfig: React.FC<CollectConfigProps> = ({
config,
onConfig,
context,
}) => {
public readonly CollectConfig: React.FC<CollectConfigProps> = ({ config, onConfig, context }) => {
const [variables, exampleUrl] = React.useMemo(
() => [this.getVariableList(context), this.getExampleUrl(context)],
[context]
@ -149,8 +137,6 @@ export class UrlDrilldown implements Drilldown<Config, ActionContext, ActionFact
);
};
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly createConfig = () => ({
url: {
template: '',

View file

@ -788,10 +788,10 @@ export class Embeddable
const { getTriggerCompatibleActions } = this.deps;
if (getTriggerCompatibleActions) {
const embeddable = this;
const actions: Array<Action<CellValueContext>> = await getTriggerCompatibleActions(
const actions: Array<Action<CellValueContext>> = (await getTriggerCompatibleActions(
CELL_VALUE_TRIGGER,
{ data, embeddable }
);
)) as Array<Action<CellValueContext>>;
return actions
.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity))
.map((action) => ({

View file

@ -12,7 +12,6 @@ import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public';
import type { ApplicationStart } from '@kbn/core/public';
import type { SerializableRecord } from '@kbn/utility-types';
import type { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public';
import { reactToUiComponent } from '@kbn/kibana-react-plugin/public';
import type {
UiActionsEnhancedDrilldownDefinition as Drilldown,
UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext,
@ -92,7 +91,7 @@ export class OpenInDiscoverDrilldown
);
};
public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig);
public readonly CollectConfig = this.ReactCollectConfig;
public readonly createConfig = () => ({
openInNewTab: true,

View file

@ -65,7 +65,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
withKibana: (comp) => {
return comp;
},
reactToUiComponent: jest.fn(),
}));
import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers';

View file

@ -54,7 +54,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
withKibana: (node) => {
return node;
},
reactToUiComponent: jest.fn(),
}));
const testingState = {

View file

@ -14,7 +14,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
withKibana: (comp) => {
return comp;
},
reactToUiComponent: jest.fn(),
}));
describe('CalendarListsHeader', () => {

View file

@ -49,7 +49,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
withKibana: (node) => {
return node;
},
reactToUiComponent: jest.fn(),
}));
import { shallowWithIntl } from '@kbn/test-jest-helpers';

View file

@ -30,7 +30,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({
withKibana: (node) => {
return node;
},
reactToUiComponent: jest.fn(),
}));
// Mock the call for loading the list of filters.

View file

@ -7,7 +7,7 @@
import { Filter, FilterStateStore } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { MlCoreSetup } from '../plugin';
import {
ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE,
@ -20,8 +20,8 @@ export const APPLY_ENTITY_FIELD_FILTERS_ACTION = 'applyEntityFieldFiltersAction'
export function createApplyEntityFieldFiltersAction(
getStartServices: MlCoreSetup['getStartServices']
) {
return createAction<AnomalyChartsFieldSelectionContext>({
): UiActionsActionDefinition<AnomalyChartsFieldSelectionContext> {
return {
id: 'apply-entity-field-filters',
type: APPLY_ENTITY_FIELD_FILTERS_ACTION,
getIconType(context: AnomalyChartsFieldSelectionContext): string {
@ -88,5 +88,5 @@ export function createApplyEntityFieldFiltersAction(
async isCompatible({ embeddable, data }) {
return embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE && data !== undefined;
},
});
};
}

View file

@ -7,7 +7,7 @@
import { Filter, FilterStateStore } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { firstValueFrom } from 'rxjs';
import { DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public';
import { MlCoreSetup } from '../plugin';
@ -21,8 +21,8 @@ const supportedApps = [DASHBOARD_APP_ID];
export function createApplyInfluencerFiltersAction(
getStartServices: MlCoreSetup['getStartServices']
) {
return createAction<SwimLaneDrilldownContext>({
): UiActionsActionDefinition<SwimLaneDrilldownContext> {
return {
id: 'apply-to-current-view',
type: APPLY_INFLUENCER_FILTERS_ACTION,
getIconType(context: SwimLaneDrilldownContext): string {
@ -85,5 +85,5 @@ export function createApplyInfluencerFiltersAction(
supportedApps.includes(appId!)
);
},
});
};
}

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public';
import { firstValueFrom } from 'rxjs';
import { MlCoreSetup } from '../plugin';
@ -19,8 +19,8 @@ const supportedApps = [DASHBOARD_APP_ID];
export function createApplyTimeRangeSelectionAction(
getStartServices: MlCoreSetup['getStartServices']
) {
return createAction<SwimLaneDrilldownContext>({
): UiActionsActionDefinition<SwimLaneDrilldownContext> {
return {
id: 'apply-time-range-selection',
type: APPLY_TIME_RANGE_SELECTION_ACTION,
getIconType(context): string {
@ -61,5 +61,5 @@ export function createApplyTimeRangeSelectionAction(
supportedApps.includes(appId!)
);
},
});
};
}

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { MlCoreSetup } from '../plugin';
export const CLEAR_SELECTION_ACTION = 'clearSelectionAction';
@ -15,8 +15,10 @@ export interface ClearSelectionContext {
updateCallback: () => void;
}
export function createClearSelectionAction(getStartServices: MlCoreSetup['getStartServices']) {
return createAction<ClearSelectionContext>({
export function createClearSelectionAction(
getStartServices: MlCoreSetup['getStartServices']
): UiActionsActionDefinition<ClearSelectionContext> {
return {
id: 'clear-selection-action',
type: CLEAR_SELECTION_ACTION,
getIconType(context): string {
@ -33,5 +35,5 @@ export function createClearSelectionAction(getStartServices: MlCoreSetup['getSta
async isCompatible({ updateCallback }) {
return typeof updateCallback === 'function';
},
});
};
}

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { MlCoreSetup } from '../plugin';
import {
@ -18,8 +18,8 @@ export const EDIT_ANOMALY_CHARTS_PANEL_ACTION = 'editAnomalyChartsPanelAction';
export function createEditAnomalyChartsPanelAction(
getStartServices: MlCoreSetup['getStartServices']
) {
return createAction<EditAnomalyChartsPanelContext>({
): UiActionsActionDefinition<EditAnomalyChartsPanelContext> {
return {
id: 'edit-anomaly-charts',
type: EDIT_ANOMALY_CHARTS_PANEL_ACTION,
getIconType(context): string {
@ -56,5 +56,5 @@ export function createEditAnomalyChartsPanelAction(
embeddable.getInput().viewMode === ViewMode.EDIT
);
},
});
};
}

View file

@ -6,15 +6,17 @@
*/
import { i18n } from '@kbn/i18n';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { MlCoreSetup } from '../plugin';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, EditSwimlanePanelContext } from '../embeddables';
export const EDIT_SWIMLANE_PANEL_ACTION = 'editSwimlanePanelAction';
export function createEditSwimlanePanelAction(getStartServices: MlCoreSetup['getStartServices']) {
return createAction<EditSwimlanePanelContext>({
export function createEditSwimlanePanelAction(
getStartServices: MlCoreSetup['getStartServices']
): UiActionsActionDefinition<EditSwimlanePanelContext> {
return {
id: 'edit-anomaly-swimlane',
type: EDIT_SWIMLANE_PANEL_ACTION,
getIconType(context): string {
@ -48,5 +50,5 @@ export function createEditSwimlanePanelAction(getStartServices: MlCoreSetup['get
embeddable.getInput().viewMode === ViewMode.EDIT
);
},
});
};
}

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { SerializableRecord } from '@kbn/utility-types';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { MlCoreSetup } from '../plugin';
import { ML_APP_LOCATOR } from '../../common/constants/locator';
import {
@ -23,8 +23,10 @@ import { ExplorerAppState } from '../../common/types/locator';
export const OPEN_IN_ANOMALY_EXPLORER_ACTION = 'openInAnomalyExplorerAction';
export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getStartServices']) {
return createAction<SwimLaneDrilldownContext | AnomalyChartsFieldSelectionContext>({
export function createOpenInExplorerAction(
getStartServices: MlCoreSetup['getStartServices']
): UiActionsActionDefinition<SwimLaneDrilldownContext | AnomalyChartsFieldSelectionContext> {
return {
id: 'open-in-anomaly-explorer',
type: OPEN_IN_ANOMALY_EXPLORER_ACTION,
getIconType(context): string {
@ -129,5 +131,5 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta
embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE
);
},
});
};
}

View file

@ -7,13 +7,15 @@
import { i18n } from '@kbn/i18n';
import type { Embeddable } from '@kbn/lens-plugin/public';
import { createAction } from '@kbn/ui-actions-plugin/public';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { MlCoreSetup } from '../plugin';
export const CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION = 'createMLADJobAction';
export function createLensVisToADJobAction(getStartServices: MlCoreSetup['getStartServices']) {
return createAction<{ embeddable: Embeddable }>({
export function createLensVisToADJobAction(
getStartServices: MlCoreSetup['getStartServices']
): UiActionsActionDefinition<{ embeddable: Embeddable }> {
return {
id: 'create-ml-ad-job-action',
type: CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION,
getIconType(context): string {
@ -66,5 +68,5 @@ export function createLensVisToADJobAction(getStartServices: MlCoreSetup['getSta
return false;
}
},
});
};
}