From 502a60a4efe478cdb6e95b6e95dc6b60e579548d Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 9 Mar 2020 09:57:15 +0100 Subject: [PATCH 01/79] Add drilldown wizard components --- .../public/components/action_wizard/index.ts | 2 +- .../public/components/index.ts} | 2 +- .../advanced_ui_actions/public/index.ts | 2 + x-pack/plugins/drilldowns/kibana.json | 5 +- .../actions/flyout_create_drilldown/index.tsx | 4 +- .../drilldown_hello_bar.scss | 5 + .../drilldown_hello_bar.story.tsx | 16 +- .../drilldown_hello_bar.tsx | 48 ++++-- .../components/drilldown_hello_bar/i18n.ts | 29 ++++ .../drilldown_picker/drilldown_picker.tsx | 21 --- .../flyout_create_drilldown.story.tsx | 24 --- .../flyout_create_drilldown.tsx | 34 ----- .../flyout_drilldown_wizard.story.tsx | 44 ++++++ .../flyout_drilldown_wizard.tsx | 139 ++++++++++++++++++ .../flyout_drilldown_wizard/i18n.ts | 42 ++++++ .../index.ts | 2 +- .../flyout_frame/flyout_frame.story.tsx | 7 + .../flyout_frame/flyout_frame.test.tsx | 4 +- .../components/flyout_frame/flyout_frame.tsx | 43 +++++- .../public/components/flyout_frame/i18n.ts | 6 +- .../flyout_list_manage_drilldowns.story.tsx | 17 +++ .../flyout_list_manage_drilldowns.tsx | 46 ++++++ .../flyout_list_manage_drilldowns/i18n.ts | 14 ++ .../flyout_list_manage_drilldowns/index.ts | 7 + .../flyout_manage_drilldowns.story.tsx | 17 +++ .../flyout_manage_drilldowns.tsx | 73 +++++++++ .../i18n.ts | 6 +- .../flyout_manage_drilldowns/index.ts | 7 + .../form_create_drilldown.story.tsx | 34 ----- .../form_create_drilldown.tsx | 52 ------- .../form_drilldown_wizard.scss | 4 + .../form_drilldown_wizard.story.tsx | 28 ++++ .../form_drilldown_wizard.test.tsx} | 20 +-- .../form_drilldown_wizard.tsx | 80 ++++++++++ .../i18n.ts | 2 +- .../index.tsx | 2 +- .../components/list_manage_drilldowns/i18n.ts | 36 +++++ .../list_manage_drilldowns/index.tsx | 7 + .../list_manage_drilldowns.story.tsx} | 9 +- .../list_manage_drilldowns.test.tsx | 61 ++++++++ .../list_manage_drilldowns.tsx | 100 +++++++++++++ .../list_manage_drilldowns/test_data.ts | 11 ++ 42 files changed, 899 insertions(+), 213 deletions(-) rename x-pack/plugins/{drilldowns/public/components/drilldown_picker/index.tsx => advanced_ui_actions/public/components/index.ts} (87%) create mode 100644 x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss create mode 100644 x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts delete mode 100644 x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx delete mode 100644 x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx delete mode 100644 x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts rename x-pack/plugins/drilldowns/public/components/{flyout_create_drilldown => flyout_drilldown_wizard}/index.ts (84%) create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx rename x-pack/plugins/drilldowns/public/components/{flyout_create_drilldown => flyout_manage_drilldowns}/i18n.ts (62%) create mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts delete mode 100644 x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx delete mode 100644 x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss create mode 100644 x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx rename x-pack/plugins/drilldowns/public/components/{form_create_drilldown/form_create_drilldown.test.tsx => form_drilldown_wizard/form_drilldown_wizard.test.tsx} (74%) create mode 100644 x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx rename x-pack/plugins/drilldowns/public/components/{form_create_drilldown => form_drilldown_wizard}/i18n.ts (94%) rename x-pack/plugins/drilldowns/public/components/{form_create_drilldown => form_drilldown_wizard}/index.tsx (85%) create mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts create mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx rename x-pack/plugins/drilldowns/public/components/{drilldown_picker/drilldown_picker.story.tsx => list_manage_drilldowns/list_manage_drilldowns.story.tsx} (57%) create mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts index ed224248ec4cd..3e7d0bf79bdc3 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ActionFactory, ActionWizard } from './action_wizard'; +export { ActionFactory, ActionWizard, ActionBaseConfig } from './action_wizard'; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx b/x-pack/plugins/advanced_ui_actions/public/components/index.ts similarity index 87% rename from x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx rename to x-pack/plugins/advanced_ui_actions/public/components/index.ts index 3be289fe6d46e..236b1a6ec4611 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_picker/index.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './drilldown_picker'; +export * from './action_wizard'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index c11c1119a9b13..0619c12b9b40e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -12,3 +12,5 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { AdvancedUiActionsPublicPlugin as Plugin }; + +export * from './components'; diff --git a/x-pack/plugins/drilldowns/kibana.json b/x-pack/plugins/drilldowns/kibana.json index b951c7dc1fc87..8372d87166364 100644 --- a/x-pack/plugins/drilldowns/kibana.json +++ b/x-pack/plugins/drilldowns/kibana.json @@ -3,8 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [ - "uiActions", - "embeddable" - ] + "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions"] } diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx index 4834cc8081374..0ce10b2a801e3 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx @@ -10,7 +10,7 @@ import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FlyoutCreateDrilldown } from '../../components/flyout_create_drilldown'; +import { FlyoutDrilldownWizard } from '../../components/flyout_drilldown_wizard'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -46,7 +46,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} />) + toMountPoint( handle.close()} />) ); } } diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss new file mode 100644 index 0000000000000..241f56731ab7b --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss @@ -0,0 +1,5 @@ +@import '../../../../../../src/legacy/ui/public/styles/_styling_constants'; + +.drdHelloBar__content .euiFlexItem { + margin: $euiSizeL; // increase spacing between elements +} diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx index 7a9e19342f27c..c4a4630397f1c 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.story.tsx @@ -8,6 +8,16 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { DrilldownHelloBar } from '.'; -storiesOf('components/DrilldownHelloBar', module).add('default', () => { - return ; -}); +const Demo = () => { + const [show, setShow] = React.useState(true); + return show ? ( + { + setShow(false); + }} + /> + ) : null; +}; + +storiesOf('components/DrilldownHelloBar', module).add('default', () => ); diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index 1ef714f7b86e2..bec9d0145c2b3 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -5,22 +5,48 @@ */ import React from 'react'; +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, + EuiLink, + EuiSpacer, + EuiButtonEmpty, +} from '@elastic/eui'; +import './drilldown_hello_bar.scss'; +import { txtHideHelpButtonLabel, txtHelpText, txtViewDocsLinkLabel } from './i18n'; export interface DrilldownHelloBarProps { docsLink?: string; + onHideClick?: () => void; } -/** - * @todo https://github.com/elastic/kibana/issues/55311 - */ -export const DrilldownHelloBar: React.FC = ({ docsLink }) => { +export const DrilldownHelloBar: React.FC = ({ + docsLink, + onHideClick = () => {}, +}) => { return ( -
-

- Drilldowns provide the ability to define a new behavior when interacting with a panel. You - can add multiple options or simply override the default filtering behavior. -

- View docs -
+ + + {txtHelpText} + {docsLink && ( + <> + + {txtViewDocsLinkLabel} + + )} + + + + {txtHideHelpButtonLabel} + + + + } + /> ); }; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts new file mode 100644 index 0000000000000..63dc95dabc0fb --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/i18n.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtHelpText = i18n.translate( + 'xpack.drilldowns.components.DrilldownHelloBar.helpText', + { + defaultMessage: + 'Drilldowns provide the ability to define a new behavior when interacting with a panel. You can add multiple options or simply override the default filtering behavior.', + } +); + +export const txtViewDocsLinkLabel = i18n.translate( + 'xpack.drilldowns.components.DrilldownHelloBar.viewDocsLinkLabel', + { + defaultMessage: 'View docs', + } +); + +export const txtHideHelpButtonLabel = i18n.translate( + 'xpack.drilldowns.components.DrilldownHelloBar.hideHelpButtonLabel', + { + defaultMessage: 'Hide', + } +); diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx deleted file mode 100644 index 3748fc666c81c..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.tsx +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -// eslint-disable-next-line -export interface DrilldownPickerProps {} - -export const DrilldownPicker: React.FC = () => { - return ( - - ); -}; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx deleted file mode 100644 index 4f024b7d9cd6a..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.story.tsx +++ /dev/null @@ -1,24 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable no-console */ - -import * as React from 'react'; -import { EuiFlyout } from '@elastic/eui'; -import { storiesOf } from '@storybook/react'; -import { FlyoutCreateDrilldown } from '.'; - -storiesOf('components/FlyoutCreateDrilldown', module) - .add('default', () => { - return ; - }) - .add('open in flyout', () => { - return ( - - - - ); - }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx deleted file mode 100644 index b45ac9197c7e0..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/flyout_create_drilldown.tsx +++ /dev/null @@ -1,34 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiButton } from '@elastic/eui'; -import { FormCreateDrilldown } from '../form_create_drilldown'; -import { FlyoutFrame } from '../flyout_frame'; -import { txtCreateDrilldown } from './i18n'; -import { FlyoutCreateDrilldownActionContext } from '../../actions'; - -export interface FlyoutCreateDrilldownProps { - context: FlyoutCreateDrilldownActionContext; - onClose?: () => void; -} - -export const FlyoutCreateDrilldown: React.FC = ({ - context, - onClose, -}) => { - const footer = ( - {}} fill> - {txtCreateDrilldown} - - ); - - return ( - - - - ); -}; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx new file mode 100644 index 0000000000000..a6395db54560c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable no-console */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutDrilldownWizard } from '.'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { urlDrilldownActionFactory } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; + +storiesOf('components/FlyoutDrilldownWizard', module) + .add('default', () => { + return ; + }) + .add('open in flyout - create', () => { + return ( + {}}> + {}} /> + + ); + }) + .add('open in flyout - edit', () => { + return ( + {}}> + {}} + initialDrilldownWizardConfig={{ + name: 'My fancy drilldown', + actionFactory: urlDrilldownActionFactory, + actionConfig: { + url: 'https://elastic.co', + openInNewTab: true, + }, + }} + mode={'edit'} + /> + + ); + }); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx new file mode 100644 index 0000000000000..fcc5bcf2fa8b8 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { FormDrilldownWizard } from '../form_drilldown_wizard'; +import { FlyoutFrame } from '../flyout_frame'; +import { + txtCreateDrilldownButtonLabel, + txtCreateDrilldownTitle, + txtDeleteDrilldownButtonLabel, + txtEditDrilldownButtonLabel, + txtEditDrilldownTitle, +} from './i18n'; +import { ActionBaseConfig, ActionFactory } from '../../../../advanced_ui_actions/public'; +import { + dashboardDrilldownActionFactory, + urlDrilldownActionFactory, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +import { DrilldownHelloBar } from '../drilldown_hello_bar'; + +export interface DrilldownWizardConfig { + name: string; + actionFactory?: ActionFactory; + actionConfig?: ActionConfig; +} + +export interface FlyoutDrilldownWizardProps< + CurrentActionConfig extends ActionBaseConfig = ActionBaseConfig +> { + onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void; + onDelete?: () => void; + onClose?: () => void; + onBack?: () => void; + + mode?: 'create' | 'edit'; + initialDrilldownWizardConfig?: DrilldownWizardConfig; + + showWelcomeMessage?: boolean; + onWelcomeHideClick?: () => void; +} + +export function FlyoutDrilldownWizard< + CurrentActionConfig extends ActionBaseConfig = ActionBaseConfig +>({ + onClose, + onBack, + onSubmit = () => {}, + initialDrilldownWizardConfig, + mode = 'create', + onDelete = () => {}, + showWelcomeMessage = false, + onWelcomeHideClick, +}: FlyoutDrilldownWizardProps) { + const [wizardConfig, setWizardConfig] = useState( + () => + initialDrilldownWizardConfig ?? { + name: '', + } + ); + + const isActionValid = (): boolean => { + if (!wizardConfig.name) return false; + if (!wizardConfig.actionFactory) return false; + if (!wizardConfig.actionConfig) return false; + + return wizardConfig.actionFactory.isValid(wizardConfig.actionConfig); + }; + + const footer = ( + { + if (isActionValid()) { + onSubmit(wizardConfig); + } + }} + fill + isDisabled={!isActionValid()} + > + {mode === 'edit' ? txtEditDrilldownButtonLabel : txtCreateDrilldownButtonLabel} + + ); + + return ( + } + > + { + setWizardConfig({ + ...wizardConfig, + name: newName, + }); + }} + actionConfig={wizardConfig.actionConfig} + onActionConfigChange={newActionConfig => { + setWizardConfig({ + ...wizardConfig, + actionConfig: newActionConfig, + }); + }} + currentActionFactory={wizardConfig.actionFactory} + onActionFactoryChange={actionFactory => { + if (!actionFactory) { + setWizardConfig({ + ...wizardConfig, + actionFactory: undefined, + actionConfig: undefined, + }); + } else { + setWizardConfig({ + ...wizardConfig, + actionFactory, + actionConfig: actionFactory.createConfig(), + }); + } + }} + actionFactories={[dashboardDrilldownActionFactory, urlDrilldownActionFactory]} + /> + {mode === 'edit' && ( + <> + + + {txtDeleteDrilldownButtonLabel} + + + )} + + ); +} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts new file mode 100644 index 0000000000000..44f4a9c041a3b --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtCreateDrilldownTitle = i18n.translate( + 'xpack.drilldowns.components.FlyoutDrilldownWizard.CreateDrilldownTitle', + { + defaultMessage: 'Create Drilldown', + } +); + +export const txtEditDrilldownTitle = i18n.translate( + 'xpack.drilldowns.components.FlyoutDrilldownWizard.EditDrilldownTitle', + { + defaultMessage: 'Edit Drilldown', + } +); + +export const txtCreateDrilldownButtonLabel = i18n.translate( + 'xpack.drilldowns.components.FlyoutDrilldownWizard.CreateDrilldownButtonLabel', + { + defaultMessage: 'Create drilldown', + } +); + +export const txtEditDrilldownButtonLabel = i18n.translate( + 'xpack.drilldowns.components.FlyoutDrilldownWizard.EditDrilldownButtonLabel', + { + defaultMessage: 'Save', + } +); + +export const txtDeleteDrilldownButtonLabel = i18n.translate( + 'xpack.drilldowns.components.FlyoutDrilldownWizard.DeleteDrilldownButtonLabel', + { + defaultMessage: 'Delete drilldown', + } +); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts similarity index 84% rename from x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts rename to x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts index ce235043b4ef6..96ed23bf112c9 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/index.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './flyout_create_drilldown'; +export * from './flyout_drilldown_wizard'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx index 2715637f6392f..cb223db556f56 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.story.tsx @@ -21,6 +21,13 @@ storiesOf('components/FlyoutFrame', module) .add('with onClose', () => { return console.log('onClose')}>test; }) + .add('with onBack', () => { + return ( + console.log('onClose')} title={'Title'}> + test + + ); + }) .add('custom footer', () => { return click me!}>test; }) diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx index b5fb52fcf5c18..0a3989487745f 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.test.tsx @@ -6,9 +6,11 @@ import React from 'react'; import { render } from 'react-dom'; -import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; +import { render as renderTestingLibrary, fireEvent, cleanup } from '@testing-library/react/pure'; import { FlyoutFrame } from '.'; +afterEach(cleanup); + describe('', () => { test('renders without crashing', () => { const div = document.createElement('div'); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index 2945cfd739482..659fc895b19a5 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -13,13 +13,16 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, + EuiButtonIcon, } from '@elastic/eui'; -import { txtClose } from './i18n'; +import { txtClose, txtBack } from './i18n'; export interface FlyoutFrameProps { title?: React.ReactNode; footer?: React.ReactNode; + banner?: React.ReactNode; onClose?: () => void; + onBack?: () => void; } /** @@ -30,11 +33,41 @@ export const FlyoutFrame: React.FC = ({ footer, onClose, children, + onBack, + banner, }) => { - const headerFragment = title && ( + const headerFragment = (title || onBack) && ( - -

{title}

+ + <> + {/* just title */} + {title && !onBack &&

{title}

} + {/* just back button */} + {!title && onBack && ( + + )} + {/* back button && title */} + {title && onBack && ( + + + + + +

{title}

+
+
+ )} +
); @@ -64,7 +97,7 @@ export const FlyoutFrame: React.FC = ({ return ( <> {headerFragment} - {children} + {children} {footerFragment} ); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts index 257d7d36dbee1..23af89ebf9bc7 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/i18n.ts @@ -6,6 +6,10 @@ import { i18n } from '@kbn/i18n'; -export const txtClose = i18n.translate('xpack.drilldowns.components.FlyoutFrame.Close', { +export const txtClose = i18n.translate('xpack.drilldowns.components.FlyoutFrame.CloseButtonLabel', { defaultMessage: 'Close', }); + +export const txtBack = i18n.translate('xpack.drilldowns.components.FlyoutFrame.BackButtonLabel', { + defaultMessage: 'Back', +}); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx new file mode 100644 index 0000000000000..d3146bbc9d3ed --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutListManageDrilldowns } from './flyout_list_manage_drilldowns'; +import { drilldowns } from '../list_manage_drilldowns/test_data'; + +storiesOf('components/FlyoutListManageDrilldowns', module).add('default', () => ( + {}}> + + +)); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx new file mode 100644 index 0000000000000..a44a7ccccb4dc --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FlyoutFrame } from '../flyout_frame'; +import { DrilldownListItem, ListManageDrilldowns } from '../list_manage_drilldowns'; +import { txtManageDrilldowns } from './i18n'; +import { DrilldownHelloBar } from '../drilldown_hello_bar'; + +export interface FlyoutListManageDrilldownsProps { + drilldowns: DrilldownListItem[]; + onClose?: () => void; + onCreate?: () => void; + onEdit?: (drilldownId: string) => void; + onDelete?: (drilldownIds: string[]) => void; + showWelcomeMessage?: boolean; + onWelcomeHideClick?: () => void; +} + +export function FlyoutListManageDrilldowns({ + drilldowns, + onClose = () => {}, + onCreate, + onDelete, + onEdit, + showWelcomeMessage = true, + onWelcomeHideClick, +}: FlyoutListManageDrilldownsProps) { + return ( + } + > + + + ); +} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts new file mode 100644 index 0000000000000..0dd4e37d4dddd --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/i18n.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtManageDrilldowns = i18n.translate( + 'xpack.drilldowns.components.FlyoutListManageDrilldowns.manageDrilldownsTitle', + { + defaultMessage: 'Manage Drilldowns', + } +); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts new file mode 100644 index 0000000000000..f8c9d224fb292 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './flyout_list_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx new file mode 100644 index 0000000000000..33feea11c4f29 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiFlyout } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import { FlyoutManageDrilldowns } from './flyout_manage_drilldowns'; +import { drilldowns } from '../list_manage_drilldowns/test_data'; + +storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( + {}}> + + +)); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx new file mode 100644 index 0000000000000..207cb3e42d2b3 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { DrilldownListItem } from '../list_manage_drilldowns'; +import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; +import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; + +export interface FlyoutManageDrilldownsProps { + drilldowns: DrilldownListItem[]; + onClose?: () => void; + showWelcomeMessage?: boolean; + onHideWelcomeMessage?: () => void; +} + +enum ViewState { + List = 'list', + Create = 'create', + Edit = 'edit', +} + +export function FlyoutManageDrilldowns({ + drilldowns, + onClose = () => {}, + showWelcomeMessage = true, + onHideWelcomeMessage, +}: FlyoutManageDrilldownsProps) { + const [viewState, setViewState] = useState(ViewState.List); + + // TODO: apparently this will be the component with all the state management and data fetching + + switch (viewState) { + case ViewState.Create: + case ViewState.Edit: + return ( + setViewState(ViewState.List)} + onDelete={() => { + setViewState(ViewState.List); + }} + onClose={() => { + onClose(); + }} + onBack={() => { + setViewState(ViewState.List); + }} + showWelcomeMessage={showWelcomeMessage} + onWelcomeHideClick={onHideWelcomeMessage} + /> + ); + case ViewState.List: + default: + return ( + { + setViewState(ViewState.Create); + }} + onEdit={() => { + setViewState(ViewState.Edit); + }} + onDelete={() => {}} + /> + ); + } +} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts similarity index 62% rename from x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts index ceabc6d3a9aa5..460fcf2e06c0e 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_create_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts @@ -6,9 +6,9 @@ import { i18n } from '@kbn/i18n'; -export const txtCreateDrilldown = i18n.translate( - 'xpack.drilldowns.components.FlyoutCreateDrilldown.CreateDrilldown', +export const txtManageDrilldowns = i18n.translate( + 'xpack.drilldowns.components.FlyoutManageDrilldowns.manageDrilldownsTitle', { - defaultMessage: 'Create drilldown', + defaultMessage: 'Manage Drilldowns', } ); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts new file mode 100644 index 0000000000000..c1c530977a122 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './flyout_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx deleted file mode 100644 index e7e1d67473e8c..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.story.tsx +++ /dev/null @@ -1,34 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable no-console */ - -import * as React from 'react'; -import { EuiFlyout } from '@elastic/eui'; -import { storiesOf } from '@storybook/react'; -import { FormCreateDrilldown } from '.'; - -const DemoEditName: React.FC = () => { - const [name, setName] = React.useState(''); - - return ; -}; - -storiesOf('components/FormCreateDrilldown', module) - .add('default', () => { - return ; - }) - .add('[name=foobar]', () => { - return ; - }) - .add('can edit name', () => ) - .add('open in flyout', () => { - return ( - - - - ); - }); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx b/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx deleted file mode 100644 index 4422de604092b..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.tsx +++ /dev/null @@ -1,52 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiForm, EuiFormRow, EuiFieldText } from '@elastic/eui'; -import { DrilldownHelloBar } from '../drilldown_hello_bar'; -import { txtNameOfDrilldown, txtUntitledDrilldown, txtDrilldownAction } from './i18n'; -import { DrilldownPicker } from '../drilldown_picker'; - -const noop = () => {}; - -export interface FormCreateDrilldownProps { - name?: string; - onNameChange?: (name: string) => void; -} - -export const FormCreateDrilldown: React.FC = ({ - name = '', - onNameChange = noop, -}) => { - const nameFragment = ( - - onNameChange(event.target.value)} - data-test-subj="dynamicActionNameInput" - /> - - ); - - const triggerPicker =
Trigger Picker will be here
; - const actionPicker = ( - - - - ); - - return ( - <> - - {nameFragment} - {triggerPicker} - {actionPicker} - - ); -}; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss new file mode 100644 index 0000000000000..b8507abb796b5 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss @@ -0,0 +1,4 @@ +.drdFormDrilldownWizard__formRow .euiFormRow__label { + font-size: #{$euiSize * 0.875}; // increase default euiFormRow label size + margin-bottom: $euiSizeS; // increase default euiFormRow label margin bottom +} diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx new file mode 100644 index 0000000000000..dbf7d4c35769c --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { FormDrilldownWizard } from '.'; + +const DemoEditName: React.FC = () => { + const [name, setName] = React.useState(''); + + return ( + <> +
name: {name}
+ + ); +}; + +storiesOf('components/FormDrilldownWizard', module) + .add('default', () => { + return ; + }) + .add('[name=foobar]', () => { + return ; + }) + .add('can edit name', () => ); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx similarity index 74% rename from x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx rename to x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx index 6691966e47e64..b4707eef79a68 100644 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/form_create_drilldown.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx @@ -6,21 +6,23 @@ import React from 'react'; import { render } from 'react-dom'; -import { FormCreateDrilldown } from '.'; -import { render as renderTestingLibrary, fireEvent } from '@testing-library/react'; +import { FormDrilldownWizard } from './form_drilldown_wizard'; +import { render as renderTestingLibrary, fireEvent, cleanup } from '@testing-library/react/pure'; import { txtNameOfDrilldown } from './i18n'; -describe('', () => { +afterEach(cleanup); + +describe('', () => { test('renders without crashing', () => { const div = document.createElement('div'); - render( {}} />, div); + render( {}} />, div); }); describe('[name=]', () => { test('if name not provided, uses to empty string', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -29,10 +31,10 @@ describe('', () => { expect(input?.value).toBe(''); }); - test('can set name input field value', () => { + test('can set initial name input field value', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -40,7 +42,7 @@ describe('', () => { expect(input?.value).toBe('foo'); - render(, div); + render(, div); expect(input?.value).toBe('bar'); }); @@ -48,7 +50,7 @@ describe('', () => { test('fires onNameChange callback on name change', () => { const onNameChange = jest.fn(); const utils = renderTestingLibrary( - + ); const input = utils.getByLabelText(txtNameOfDrilldown); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx new file mode 100644 index 0000000000000..19ec5b7506c1b --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import './form_drilldown_wizard.scss'; +import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; +import { + ActionBaseConfig, + ActionFactory, + ActionWizard, +} from '../../../../advanced_ui_actions/public'; + +const noop = () => {}; + +export interface FormDrilldownWizardProps { + name?: string; + onNameChange?: (name: string) => void; + + currentActionFactory?: ActionFactory; + onActionFactoryChange?: (actionFactory: ActionFactory | null) => void; + + actionConfig?: ActionBaseConfig; + onActionConfigChange?: (config: ActionBaseConfig) => void; + + actionFactories?: Array>; +} + +export const FormDrilldownWizard: React.FC = ({ + name = '', + actionConfig, + currentActionFactory, + onNameChange = noop, + onActionConfigChange = noop, + onActionFactoryChange = noop, + actionFactories = [], +}) => { + const nameFragment = ( + + onNameChange(event.target.value)} + data-test-subj="dynamicActionNameInput" + /> + + ); + + const actionWizard = ( + + onActionFactoryChange(actionFactory)} + onConfigChange={config => onActionConfigChange(config)} + /> + + ); + + return ( + <> + + + {nameFragment} + + {actionWizard} + + + ); +}; diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts similarity index 94% rename from x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts index 4c0e287935edd..2dfcd917a7900 100644 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts @@ -23,6 +23,6 @@ export const txtUntitledDrilldown = i18n.translate( export const txtDrilldownAction = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.drilldownAction', { - defaultMessage: 'Drilldown action', + defaultMessage: 'Drilldown action:', } ); diff --git a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx similarity index 85% rename from x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx rename to x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx index c2c5a7e435b39..4aea824de00d7 100644 --- a/x-pack/plugins/drilldowns/public/components/form_create_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './form_create_drilldown'; +export * from './form_drilldown_wizard'; diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts new file mode 100644 index 0000000000000..fbc7c9dcfb4a1 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/i18n.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtCreateDrilldown = i18n.translate( + 'xpack.drilldowns.components.ListManageDrilldowns.createDrilldownButtonLabel', + { + defaultMessage: 'Create new', + } +); + +export const txtEditDrilldown = i18n.translate( + 'xpack.drilldowns.components.ListManageDrilldowns.editDrilldownButtonLabel', + { + defaultMessage: 'Edit', + } +); + +export const txtDeleteDrilldowns = (count: number) => + i18n.translate('xpack.drilldowns.components.ListManageDrilldowns.deleteDrilldownsButtonLabel', { + defaultMessage: 'Delete ({count})', + values: { + count, + }, + }); + +export const txtSelectDrilldown = i18n.translate( + 'xpack.drilldowns.components.ListManageDrilldowns.selectThisDrilldownCheckboxLabel', + { + defaultMessage: 'Select this drilldown', + } +); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx new file mode 100644 index 0000000000000..82b6ce27af6d4 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/index.tsx @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './list_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx similarity index 57% rename from x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx rename to x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx index 5627a5d6f4522..ae1b063449928 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_picker/drilldown_picker.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx @@ -6,8 +6,9 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; -import { DrilldownPicker } from '.'; +import { ListManageDrilldowns } from './list_manage_drilldowns'; +import { drilldowns } from './test_data'; -storiesOf('components/DrilldownPicker', module).add('default', () => { - return ; -}); +storiesOf('components/ListManageDrilldowns', module).add('default', () => ( + +)); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx new file mode 100644 index 0000000000000..2d8a3ef8440cb --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { cleanup, fireEvent, render } from '@testing-library/react/pure'; +import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global +import { drilldowns } from './test_data'; +import { ListManageDrilldowns, TEST_SUBJ_DRILLDOWN_ITEM } from './list_manage_drilldowns'; + +// TODO: for some reason global cleanup from RTL doesn't work +// afterEach is not available for it globally during setup +afterEach(cleanup); + +test('Render list of drilldowns', () => { + const screen = render(); + expect(screen.getAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(drilldowns.length); +}); + +test('Emit onEdit() when clicking on edit drilldown', () => { + const fn = jest.fn(); + const screen = render(); + + const editButtons = screen.getAllByText('Edit'); + expect(editButtons).toHaveLength(drilldowns.length); + fireEvent.click(editButtons[1]); + expect(fn).toBeCalledWith(drilldowns[1].id); +}); + +test('Emit onCreate() when clicking on create drilldown', () => { + const fn = jest.fn(); + const screen = render(); + fireEvent.click(screen.getByText('Create new')); + expect(fn).toBeCalled(); +}); + +test('Delete button is not visible when non is selected', () => { + const fn = jest.fn(); + const screen = render(); + expect(screen.queryByText(/Delete/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Create/i)).toBeInTheDocument(); +}); + +test('Can delete drilldowns', () => { + const fn = jest.fn(); + const screen = render(); + + const checkboxes = screen.getAllByLabelText(/Select this drilldown/i); + expect(checkboxes).toHaveLength(3); + + fireEvent.click(checkboxes[1]); + fireEvent.click(checkboxes[2]); + + expect(screen.queryByText(/Create/i)).not.toBeInTheDocument(); + + fireEvent.click(screen.getByText(/Delete \(2\)/i)); + + expect(fn).toBeCalledWith([drilldowns[1].id, drilldowns[2].id]); +}); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx new file mode 100644 index 0000000000000..a9895444558cd --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButton, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { + txtEditDrilldown, + txtCreateDrilldown, + txtDeleteDrilldowns, + txtSelectDrilldown, +} from './i18n'; + +// TODO: interface is temporary +export interface DrilldownListItem { + id: string; + actionTypeDisplayName: string; + name: string; +} + +export interface ListManageDrilldownsProps { + drilldowns: DrilldownListItem[]; + + onEdit?: (id: string) => void; + onCreate?: () => void; + onDelete?: (ids: string[]) => void; +} + +const noop = () => {}; + +export const TEST_SUBJ_DRILLDOWN_ITEM = 'list-manage-drilldowns-item'; + +export function ListManageDrilldowns({ + drilldowns, + onEdit = noop, + onCreate = noop, + onDelete = noop, +}: ListManageDrilldownsProps) { + const [selectedDrilldowns, setSelectedDrilldowns] = useState([]); + + const columns: Array> = [ + { + field: 'name', + name: 'Name', + truncateText: true, + }, + { + field: 'actionTypeDisplayName', + name: 'Action', + truncateText: true, + }, + { + render: (drilldown: DrilldownListItem) => ( + onEdit(drilldown.id)}> + {txtEditDrilldown} + + ), + }, + ]; + + return ( + <> + { + setSelectedDrilldowns(selection.map(drilldown => drilldown.id)); + }, + selectableMessage: () => txtSelectDrilldown, + }} + rowProps={{ + 'data-test-subj': TEST_SUBJ_DRILLDOWN_ITEM, + 'data-testid': TEST_SUBJ_DRILLDOWN_ITEM, + }} + hasActions={true} + /> + + {selectedDrilldowns.length === 0 ? ( + onCreate()}> + {txtCreateDrilldown} + + ) : ( + onDelete(selectedDrilldowns)}> + {txtDeleteDrilldowns(selectedDrilldowns.length)} + + )} + + ); +} diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts new file mode 100644 index 0000000000000..862c3efafa189 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const drilldowns = [ + { id: '1', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 1' }, + { id: '2', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 2' }, + { id: '3', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 3' }, +]; From a7ed6ab14f9f2176dda5d4c0035634909679173f Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 9 Mar 2020 10:00:42 +0100 Subject: [PATCH 02/79] Dynamic actions (#58216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add DynamicAction and FactoryAction types * feat: 🎸 add Mutable type to @kbn/utility-types * feat: 🎸 add ActionInternal and ActionContract * chore: 🤖 remove unused file * feat: 🎸 improve action interfaces * docs: ✏️ add JSDocs * feat: 🎸 simplify ui_actions interfaces * fix: 🐛 fix TypeScript types * feat: 🎸 add AbstractPresentable interface * feat: 🎸 add AbstractConfigurable interface * feat: 🎸 use AbstractPresentable in ActionInternal * test: 💍 fix ui_actions Jest tests * feat: 🎸 add state container to action * perf: ⚡️ convert MenuItem to React component on Action instance * refactor: 💡 rename AbsractPresentable -> Presentable * refactor: 💡 rename AbstractConfigurable -> Configurable * feat: 🎸 add Storybook to ui_actions * feat: 🎸 add component * feat: 🎸 improve component * chore: 🤖 use .story file extension prefix for Storybook * feat: 🎸 improve component * feat: 🎸 show error if dynamic action has CollectConfig missing * feat: 🎸 render sample action configuration component * feat: 🎸 connect action config to * feat: 🎸 improve stories * test: 💍 add ActionInternal serialize/deserialize tests * feat: 🎸 add ActionContract * feat: 🎸 split action Context into Execution and Presentation * fix: 🐛 fix TypeScript error * refactor: 💡 extract state container hooks to module scope * docs: ✏️ fix typos * chore: 🤖 remove Mutable type * test: 💍 don't cast to any getActions() function * style: 💄 avoid using unnecessary types * chore: 🤖 address PR review comments * chore: 🤖 rename ActionContext generic * chore: 🤖 remove order from state container * chore: 🤖 remove deprecation notice on getHref * test: 💍 fix tests after order field change * remove comments Co-authored-by: Matt Kime Co-authored-by: Elastic Machine --- src/dev/storybook/aliases.ts | 3 +- .../lib/panel/embeddable_panel.test.tsx | 14 ++- .../public/lib/panel/embeddable_panel.tsx | 2 +- .../create_state_container_react_helpers.ts | 69 ++++++++---- .../ui_actions/public/actions/action.ts | 50 ++++++++- .../public/actions/action_contract.ts | 38 +++++++ .../public/actions/action_internal.test.ts | 104 ++++++++++++++++++ .../public/actions/action_internal.ts | 96 ++++++++++++++++ .../public/actions/action_state_container.ts | 51 +++++++++ .../public/actions/dynamic_action_storage.ts | 44 ++++++++ .../ui_actions/public/actions/index.ts | 2 + .../configure_action.story.tsx | 55 +++++++++ .../configure_action/configure_action.tsx | 48 ++++++++ .../components/configure_action/i18n.ts | 24 ++++ .../components/configure_action/index.tsx | 20 ++++ .../action_identifier.tsx | 43 ++++++++ .../error_configure_action.story.tsx | 31 ++++++ .../error_configure_action.tsx | 40 +++++++ .../components/error_configure_action/i18n.ts | 27 +++++ .../error_configure_action/index.tsx | 20 ++++ .../build_eui_context_menu_panels.tsx | 20 ++-- src/plugins/ui_actions/public/index.ts | 10 +- .../public/service/ui_actions_service.test.ts | 7 +- .../public/service/ui_actions_service.ts | 32 ++++-- .../public/tests/get_trigger_actions.test.ts | 5 +- .../tests/test_samples/go_to_url_action.tsx | 67 +++++++++++ .../public/tests/test_samples/index.ts | 2 + src/plugins/ui_actions/public/types.ts | 4 +- .../ui_actions/public/util/configurable.ts | 55 +++++++++ src/plugins/ui_actions/public/util/index.ts | 21 ++++ .../ui_actions/public/util/presentable.ts | 58 ++++++++++ src/plugins/ui_actions/scripts/storybook.js | 26 +++++ 32 files changed, 1032 insertions(+), 56 deletions(-) create mode 100644 src/plugins/ui_actions/public/actions/action_contract.ts create mode 100644 src/plugins/ui_actions/public/actions/action_internal.test.ts create mode 100644 src/plugins/ui_actions/public/actions/action_internal.ts create mode 100644 src/plugins/ui_actions/public/actions/action_state_container.ts create mode 100644 src/plugins/ui_actions/public/actions/dynamic_action_storage.ts create mode 100644 src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx create mode 100644 src/plugins/ui_actions/public/components/configure_action/configure_action.tsx create mode 100644 src/plugins/ui_actions/public/components/configure_action/i18n.ts create mode 100644 src/plugins/ui_actions/public/components/configure_action/index.tsx create mode 100644 src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx create mode 100644 src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx create mode 100644 src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx create mode 100644 src/plugins/ui_actions/public/components/error_configure_action/i18n.ts create mode 100644 src/plugins/ui_actions/public/components/error_configure_action/index.tsx create mode 100644 src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx create mode 100644 src/plugins/ui_actions/public/util/configurable.ts create mode 100644 src/plugins/ui_actions/public/util/index.ts create mode 100644 src/plugins/ui_actions/public/util/presentable.ts create mode 100644 src/plugins/ui_actions/scripts/storybook.js diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 8ed64f004c9be..52f618f611ca4 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,6 +18,7 @@ */ export const storybookAliases = { + advanced_ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js', apm: 'x-pack/legacy/plugins/apm/scripts/storybook.js', canvas: 'x-pack/legacy/plugins/canvas/scripts/storybook_new.js', codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts', @@ -25,5 +26,5 @@ export const storybookAliases = { embeddable: 'src/plugins/embeddable/scripts/storybook.js', infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', siem: 'x-pack/legacy/plugins/siem/scripts/storybook.js', - ui_actions: 'x-pack/plugins/advanced_ui_actions/scripts/storybook.js', + ui_actions: 'src/plugins/ui_actions/scripts/storybook.js', }; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index fdff82e63faec..505399f3150be 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -44,7 +44,7 @@ import { import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; -const actionRegistry = new Map>(); +const actionRegistry = new Map(); const triggerRegistry = new Map(); const embeddableFactories = new Map(); const getEmbeddableFactory: GetEmbeddableFactory = (id: string) => embeddableFactories.get(id); @@ -213,13 +213,17 @@ const renderInEditModeAndOpenContextMenu = async ( }; test('HelloWorldContainer in edit mode hides disabledActions', async () => { - const action: Action = { + const action = { id: 'FOO', type: 'FOO' as ActionType, getIconType: () => undefined, getDisplayName: () => 'foo', isCompatible: async () => true, execute: async () => {}, + order: 10, + getHref: () => { + return undefined; + }, }; const getActions = () => Promise.resolve([action]); @@ -245,13 +249,17 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { }); test('HelloWorldContainer hides disabled badges', async () => { - const action: Action = { + const action = { id: 'BAR', type: 'BAR' as ActionType, getIconType: () => undefined, getDisplayName: () => 'bar', isCompatible: async () => true, execute: async () => {}, + order: 10, + getHref: () => { + return undefined; + }, }; const getActions = () => Promise.resolve([action]); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 28474544f40b5..fc460fbcc17e9 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -245,7 +245,7 @@ export class EmbeddablePanel extends React.Component { new EditPanelAction(this.props.getEmbeddableFactory), ]; - const sorted = actions + const sorted = (actions as Array>) .concat(extraActions) .sort((a: Action, b: Action) => { const bOrder = b.order || 0; diff --git a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts index 36903f2d7c90f..90823359359a1 100644 --- a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts +++ b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts @@ -24,15 +24,58 @@ import { Comparator, Connect, StateContainer, UnboxState } from './types'; const { useContext, useLayoutEffect, useRef, createElement: h } = React; +/** + * Returns the latest state of a state container. + * + * @param container State container which state to track. + */ +export const useContainerState = >( + container: Container +): UnboxState => useObservable(container.state$, container.get()); + +/** + * Apply selector to state container to extract only needed information. Will + * re-render your component only when the section changes. + * + * @param container State container which state to track. + * @param selector Function used to pick parts of state. + * @param comparator Comparator function used to memoize previous result, to not + * re-render React component if state did not change. By default uses + * `fast-deep-equal` package. + */ +export const useContainerSelector = , Result>( + container: Container, + selector: (state: UnboxState) => Result, + comparator: Comparator = defaultComparator +): Result => { + const { state$, get } = container; + const lastValueRef = useRef(get()); + const [value, setValue] = React.useState(() => { + const newValue = selector(get()); + lastValueRef.current = newValue; + return newValue; + }); + useLayoutEffect(() => { + const subscription = state$.subscribe((currentState: UnboxState) => { + const newValue = selector(currentState); + if (!comparator(lastValueRef.current, newValue)) { + lastValueRef.current = newValue; + setValue(newValue); + } + }); + return () => subscription.unsubscribe(); + }, [state$, comparator]); + return value; +}; + export const createStateContainerReactHelpers = >() => { const context = React.createContext(null as any); const useContainer = (): Container => useContext(context); const useState = (): UnboxState => { - const { state$, get } = useContainer(); - const value = useObservable(state$, get()); - return value; + const container = useContainer(); + return useContainerState(container); }; const useTransitions: () => Container['transitions'] = () => useContainer().transitions; @@ -41,24 +84,8 @@ export const createStateContainerReactHelpers = ) => Result, comparator: Comparator = defaultComparator ): Result => { - const { state$, get } = useContainer(); - const lastValueRef = useRef(get()); - const [value, setValue] = React.useState(() => { - const newValue = selector(get()); - lastValueRef.current = newValue; - return newValue; - }); - useLayoutEffect(() => { - const subscription = state$.subscribe((currentState: UnboxState) => { - const newValue = selector(currentState); - if (!comparator(lastValueRef.current, newValue)) { - lastValueRef.current = newValue; - setValue(newValue); - } - }); - return () => subscription.unsubscribe(); - }, [state$, comparator]); - return value; + const container = useContainer(); + return useContainerSelector(container, selector, comparator); }; const connect: Connect> = mapStateToProp => component => props => diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 2b2fc004a84c6..789542766f5f5 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -19,10 +19,13 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; import { ActionType, ActionContextMapping } from '../types'; +import { Presentable } from '../util/presentable'; +import { Configurable } from '../util/configurable'; export type ActionByType = Action; -export interface Action { +export interface Action + extends Partial> { /** * Determined the order when there is more than one action matched to a trigger. * Higher numbers are displayed first. @@ -72,3 +75,48 @@ export interface Action { */ execute(context: Context): Promise; } + +/** + * A convenience interface used to register an action. + */ +export interface ActionDefinition< + Context extends object = object, + Config extends object | undefined = undefined +> extends Partial>, Partial> { + /** + * ID of the action that uniquely identifies this action in the actions registry. + */ + readonly id: string; + + /** + * ID of the factory for this action. Used to construct dynamic actions. + */ + readonly type?: ActionType; + + getHref?(context: Context): string | undefined; + + /** + * Executes the action. + */ + execute(context: Context): Promise; +} + +export type AnyActionDefinition = ActionDefinition; +export type ActionContext = A extends ActionDefinition ? Context : never; +export type ActionConfig = A extends ActionDefinition ? Config : never; + +/** + * A convenience interface used to register a dynamic action. + * + * A dynamic action is one that can be create by user and registered into the + * actions registry at runtime. User can also provide custom config for this + * action. And dynamic actions can be serialized for storage and deserialized + * back. + */ +export type DynamicActionDefinition< + Context extends object = object, + Config extends object | undefined = undefined +> = ActionDefinition & + Required, 'CollectConfig' | 'defaultConfig' | 'type'>>; + +export type AnyDynamicActionDefinition = DynamicActionDefinition; diff --git a/src/plugins/ui_actions/public/actions/action_contract.ts b/src/plugins/ui_actions/public/actions/action_contract.ts new file mode 100644 index 0000000000000..9adba9313a36d --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_contract.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ActionInternal } from './action_internal'; +import { AnyActionDefinition } from './action'; + +/** + * Action representation that is exposed out to other plugins. + */ +export type ActionContract = Pick< + ActionInternal, + | 'id' + | 'type' + | 'order' + | 'getIconType' + | 'getDisplayName' + | 'isCompatible' + | 'getHref' + | 'execute' +>; + +export type AnyActionContract = ActionContract; diff --git a/src/plugins/ui_actions/public/actions/action_internal.test.ts b/src/plugins/ui_actions/public/actions/action_internal.test.ts new file mode 100644 index 0000000000000..6f1528fea5f42 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_internal.test.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ActionDefinition } from './action'; +import { ActionInternal } from './action_internal'; +import { ActionType } from '../types'; + +const defaultActionDef: ActionDefinition = { + id: 'test-action', + execute: jest.fn(), +}; + +describe('ActionInternal', () => { + test('can instantiate from action definition', () => { + const action = new ActionInternal(defaultActionDef); + expect(action.id).toBe('test-action'); + }); + + describe('serialize()', () => { + test('can serialize very simple action', () => { + const action = new ActionInternal(defaultActionDef); + const serialized = action.serialize(); + + expect(serialized).toMatchObject({ + id: 'test-action', + state: expect.any(Object), + }); + }); + + test('can serialize action with modified state', () => { + const action = new ActionInternal({ + ...defaultActionDef, + type: 'ACTION_TYPE' as ActionType, + order: 11, + }); + action.state.transitions.setConfig({ foo: 'bar' }); + action.state.transitions.setName('qux'); + + const serialized = action.serialize(); + + expect(serialized).toMatchObject({ + id: 'test-action', + type: 'ACTION_TYPE', + state: { + name: 'qux', + config: { + foo: 'bar', + }, + }, + }); + }); + }); + + describe('deserialize', () => { + const serialized = { + id: 'id', + type: 'type', + state: { + name: 'name', + order: 0, + config: { + foo: 'foo', + }, + }, + }; + + test('can deserialize action state', () => { + const action = new ActionInternal({ + ...defaultActionDef, + }); + + action.deserialize(serialized); + + expect(action.state.get()).toMatchObject(serialized.state); + }); + + test('does not overwrite action id and type', () => { + const action = new ActionInternal({ + ...defaultActionDef, + }); + + action.deserialize(serialized); + + expect(action.id).toBe('test-action'); + expect(action.type).toBe(''); + }); + }); +}); diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts new file mode 100644 index 0000000000000..0ff139ff7ae4f --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Action, ActionContext, AnyActionDefinition } from './action'; +import { Presentable } from '../util/presentable'; +import { createActionStateContainer, ActionState } from './action_state_container'; +import { uiToReactComponent } from '../../../kibana_react/public'; +import { ActionContract } from './action_contract'; +import { ActionType } from '../types'; + +export class ActionInternal + implements Action>, Presentable> { + constructor(public readonly definition: A) {} + + public readonly id: string = this.definition.id; + public readonly type: ActionType = this.definition.type || ''; + public readonly order: number = this.definition.order || 0; + public readonly MenuItem? = this.definition.MenuItem; + public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; + public readonly CollectConfig? = this.definition.CollectConfig; + public readonly ReactCollectConfig? = this.CollectConfig + ? uiToReactComponent(this.CollectConfig) + : undefined; + + public get contract(): ActionContract { + return this; + } + + public readonly state = createActionStateContainer({ + name: '', + config: this.definition.defaultConfig || {}, + }); + + public execute(context: ActionContext) { + return this.definition.execute(context); + } + + public getIconType(context: ActionContext): string | undefined { + if (!this.definition.getIconType) return undefined; + return this.definition.getIconType(context); + } + + public getDisplayName(context: ActionContext): string { + if (!this.definition.getDisplayName) return ''; + return this.definition.getDisplayName(context); + } + + public async isCompatible(context: ActionContext): Promise { + if (!this.definition.isCompatible) return true; + return await this.definition.isCompatible(context); + } + + public getHref(context: ActionContext): string | undefined { + if (!this.definition.getHref) return undefined; + return this.definition.getHref(context); + } + + serialize(): SerializedAction { + const state = this.state.get(); + const serialized: SerializedAction = { + id: this.id, + type: this.type || '', + state, + }; + + return serialized; + } + + deserialize({ state }: SerializedAction) { + this.state.set(state); + } +} + +export type AnyActionInternal = ActionInternal; + +export interface SerializedAction { + readonly id: string; + readonly type: string; + readonly state: ActionState; +} diff --git a/src/plugins/ui_actions/public/actions/action_state_container.ts b/src/plugins/ui_actions/public/actions/action_state_container.ts new file mode 100644 index 0000000000000..ce571a95acddf --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_state_container.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createStateContainer, StateContainer } from '../../../kibana_utils/common'; + +export interface ActionState { + readonly name: string; + readonly config: Config; +} + +export interface ActionStateTransitions { + setName: (state: ActionState) => (name: string) => ActionState; + setConfig: (state: ActionState) => (config: Config) => ActionState; +} + +export type ActionStateContainer = StateContainer< + ActionState, + ActionStateTransitions, + {} +>; + +export const defaultState: ActionState = { + name: '', + config: {}, +}; + +const pureTransitions: ActionStateTransitions = { + setName: state => name => ({ ...state, name }), + setConfig: state => config => ({ ...state, config }), +}; + +export const createActionStateContainer = ( + state: Partial> +): ActionStateContainer => + createStateContainer({ ...defaultState, ...state } as ActionState, pureTransitions); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts new file mode 100644 index 0000000000000..69f218171520c --- /dev/null +++ b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SerializedAction } from './action_internal'; + +/** + * Serialized representation of event-action pair, used to persist in storage. + */ +export interface SerializedEvent { + eventId: string; + triggerId: string; + action: SerializedAction; +} + +/** + * This interface needs to be implemented by dynamic action users if they + * want to persist the dynamic actions. It has a default implementation in + * Embeddables, however one can use the dynamic actions without Embeddables, + * in that case they have to implement this interface. + */ +export interface ActionStorage { + create(event: SerializedEvent): Promise; + update(event: SerializedEvent): Promise; + remove(eventId: string): Promise; + read(eventId: string): Promise; + count(): Promise; + list(): Promise; +} diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 64bfd368e3dfa..573eaca74dec2 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -18,5 +18,7 @@ */ export * from './action'; +export * from './action_internal'; +export * from './action_contract'; export * from './create_action'; export * from './incompatible_action_error'; diff --git a/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx b/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx new file mode 100644 index 0000000000000..f4aaa67d43d76 --- /dev/null +++ b/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import useObservable from 'react-use/lib/useObservable'; +import { ConfigureAction } from '.'; +import { createSampleGoToUrlAction } from '../../tests'; +import { ActionInternal } from '../../actions'; + +const action = new ActionInternal(createSampleGoToUrlAction()); +const actionWithPresetConfig = new ActionInternal(createSampleGoToUrlAction()); +actionWithPresetConfig.state.transitions.setConfig({ + url: 'http://google.com', + openInNewTab: true, +}); +const actionMissingCollectConfig = new ActionInternal({ + ...createSampleGoToUrlAction(), + CollectConfig: undefined, +}); + +const DemoDefault: React.FC = () => { + useObservable(action.state.state$); + + return ( +
+ +
+
+
+
{JSON.stringify(action.serialize(), null, 4)}
+
+ ); +}; + +storiesOf('components/ConfigureAction', module) + .add('default', () => ) + .add('with preset config', () => ) + .add('missing CollectConfig', () => ); diff --git a/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx b/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx new file mode 100644 index 0000000000000..16629f4c092da --- /dev/null +++ b/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiForm } from '@elastic/eui'; +import { AnyActionInternal } from '../../actions'; +import { ErrorConfigureAction } from '../error_configure_action'; +import { txtMissingCollectConfig } from './i18n'; +import { useContainerState } from '../../../../kibana_utils/common'; + +export interface ConfigureActionProps { + context?: unknown; + action: AnyActionInternal; +} + +export const ConfigureAction: React.FC = ({ context, action }) => { + const { config } = useContainerState(action.state); + + if (!action.ReactCollectConfig) { + return ; + } + + return ( + + + + ); +}; diff --git a/src/plugins/ui_actions/public/components/configure_action/i18n.ts b/src/plugins/ui_actions/public/components/configure_action/i18n.ts new file mode 100644 index 0000000000000..c574aef9c24fe --- /dev/null +++ b/src/plugins/ui_actions/public/components/configure_action/i18n.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtMissingCollectConfig = i18n.translate('uiActions.components.missingCollectConfig', { + defaultMessage: 'Dynamic action must have CollectConfig component defined.', +}); diff --git a/src/plugins/ui_actions/public/components/configure_action/index.tsx b/src/plugins/ui_actions/public/components/configure_action/index.tsx new file mode 100644 index 0000000000000..f876aba7c13ec --- /dev/null +++ b/src/plugins/ui_actions/public/components/configure_action/index.tsx @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './configure_action'; diff --git a/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx b/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx new file mode 100644 index 0000000000000..94c3f7b96b8c1 --- /dev/null +++ b/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiCode } from '@elastic/eui'; +import { AnyActionInternal } from '../../actions'; + +export interface ActionIdentifierProps { + action: AnyActionInternal; +} + +export const ActionIdentifier: React.FC = ({ action }) => ( +

+ {action.id && ( + <> + Action ID: {action.id} +
+ + )} + {action.type && ( + <> + Action type: {action.type} +
+ + )} +

+); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx new file mode 100644 index 0000000000000..5d64bbd813164 --- /dev/null +++ b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { ErrorConfigureAction } from '.'; +import { createSampleGoToUrlAction } from '../../tests'; +import { ActionInternal } from '../../actions'; + +const action = new ActionInternal(createSampleGoToUrlAction()); + +storiesOf('components/ErrorConfigureAction', module) + .add('default', () => ) + .add('with action', () => ) + .add('with action and message', () => ); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx new file mode 100644 index 0000000000000..09fcfcd1d19db --- /dev/null +++ b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { txtSorryActionConfigurationError } from './i18n'; +import { AnyActionInternal } from '../../actions'; +import { ActionIdentifier } from './action_identifier'; + +export interface ErrorConfigureActionProps { + msg?: React.ReactNode; + action?: AnyActionInternal; +} + +export const ErrorConfigureAction: React.FC = ({ + action, + msg, + children, +}) => ( + + {(msg || children) &&

{msg || children}

} + {action && } +
+); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts b/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts new file mode 100644 index 0000000000000..ec0c3c533b20a --- /dev/null +++ b/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtSorryActionConfigurationError = i18n.translate( + 'uiActions.components.sorryActionConfigurationError', + { + defaultMessage: 'Sorry, action configuration error', + } +); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/index.tsx b/src/plugins/ui_actions/public/components/error_configure_action/index.tsx new file mode 100644 index 0000000000000..a38fe0fb8e190 --- /dev/null +++ b/src/plugins/ui_actions/public/components/error_configure_action/index.tsx @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './error_configure_action'; diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 3dce2c1f4c257..327f0ec15137f 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -27,16 +27,16 @@ import { Action } from '../actions'; /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. */ -export async function buildContextMenuForActions({ +export async function buildContextMenuForActions({ actions, actionContext, closeMenu, }: { - actions: Array>; - actionContext: A; + actions: Array>; + actionContext: Context; closeMenu: () => void; }): Promise { - const menuItems = await buildEuiContextMenuPanelItems({ + const menuItems = await buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, @@ -54,13 +54,13 @@ export async function buildContextMenuForActions({ /** * Transform an array of Actions into the shape needed to build an EUIContextMenu */ -async function buildEuiContextMenuPanelItems({ +async function buildEuiContextMenuPanelItems({ actions, actionContext, closeMenu, }: { - actions: Array>; - actionContext: A; + actions: Array>; + actionContext: Context; closeMenu: () => void; }) { const items: EuiContextMenuPanelItemDescriptor[] = []; @@ -90,13 +90,13 @@ async function buildEuiContextMenuPanelItems({ * @param {Embeddable} embeddable * @return {EuiContextMenuPanelItemDescriptor} */ -function convertPanelActionToContextMenuItem({ +function convertPanelActionToContextMenuItem({ action, actionContext, closeMenu, }: { - action: Action; - actionContext: A; + action: Action; + actionContext: Context; closeMenu: () => void; }): EuiContextMenuPanelItemDescriptor { const menuPanelItem: EuiContextMenuPanelItemDescriptor = { diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 79b8e1474f6c2..1844388b91884 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -26,7 +26,15 @@ export function plugin(initializerContext: PluginInitializerContext) { export { UiActionsSetup, UiActionsStart } from './plugin'; export { UiActionsServiceParams, UiActionsService } from './service'; -export { Action, createAction, IncompatibleActionError } from './actions'; +export { + Action, + createAction, + IncompatibleActionError, + ActionDefinition as UiActionsActionDefinition, + ActionInternal as UiActionsActionInternal, + ActionContract as UiActionsActionContract, +} from './actions'; +export { CollectConfigProps as UiActionsCollectConfigProps } from './util'; export { buildContextMenuForActions } from './context_menu'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index bdf71a25e6dbc..c9395e15854ec 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -18,7 +18,7 @@ */ import { UiActionsService } from './ui_actions_service'; -import { Action, createAction } from '../actions'; +import { Action, ActionInternal, createAction } from '../actions'; import { createHelloWorldAction } from '../tests/test_samples'; import { ActionRegistry, TriggerRegistry, TriggerId, ActionType } from '../types'; import { Trigger } from '../triggers'; @@ -143,7 +143,8 @@ describe('UiActionsService', () => { const list1 = service.getTriggerActions(FOO_TRIGGER); expect(list1).toHaveLength(1); - expect(list1).toEqual([action1]); + expect(list1[0]).toBeInstanceOf(ActionInternal); + expect(list1[0].id).toBe(action1.id); service.attachAction(FOO_TRIGGER, action2); const list2 = service.getTriggerActions(FOO_TRIGGER); @@ -164,7 +165,7 @@ describe('UiActionsService', () => { service.registerAction(helloWorldAction); expect(actions.size - length).toBe(1); - expect(actions.get(helloWorldAction.id)).toBe(helloWorldAction); + expect((actions.get(helloWorldAction.id) as any).id).toBe(helloWorldAction.id); }); test('getTriggerCompatibleActions returns attached actions', async () => { diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index f7718e63773f5..4bba77a4e2303 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -25,7 +25,13 @@ import { TriggerContextMapping, ActionType, } from '../types'; -import { Action, ActionByType } from '../actions'; +import { + ActionDefinition, + ActionInternal, + AnyActionInternal, + Action, + ActionByType, +} from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; import { TriggerContract } from '../triggers/trigger_contract'; @@ -76,20 +82,22 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = (action: ActionByType) => { - if (this.actions.has(action.id)) { - throw new Error(`Action [action.id = ${action.id}] already registered.`); + public readonly registerAction = >(definition: A) => { + if (this.actions.has(definition.id)) { + throw new Error(`Action [action.id = ${definition.id}] already registered.`); } - this.actions.set(action.id, action); + this.actions.set(definition.id, new ActionInternal(definition)); }; - public readonly getAction = (id: string): ActionByType => { + public readonly getAction = >( + id: string + ): ActionInternal => { if (!this.actions.has(id)) { throw new Error(`Action [action.id = ${id}] not registered.`); } - return this.actions.get(id) as ActionByType; + return this.actions.get(id) as ActionInternal; }; public readonly attachAction = ( @@ -102,7 +110,8 @@ export class UiActionsService { this.registerAction(action); } else { const registeredAction = this.actions.get(action.id); - if (registeredAction !== action) { + // todo - verify this + if (registeredAction!.id !== action.id) { throw new Error(`A different action instance with this id is already registered.`); } } @@ -147,9 +156,10 @@ export class UiActionsService { const actionIds = this.triggerToActions.get(triggerId); - const actions = actionIds!.map(actionId => this.actions.get(actionId)).filter(Boolean) as Array< - Action - >; + const actions = actionIds! + .map(actionId => this.actions.get(actionId) as AnyActionInternal) + .filter(Boolean) + .map(({ contract }) => contract) as Array>; return actions as Array>>; }; diff --git a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts index f5a6a96fb41a4..53960c2d47266 100644 --- a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Action } from '../actions'; +import { ActionInternal, Action } from '../actions'; import { uiActionsPluginMock } from '../mocks'; import { TriggerId, ActionType } from '../types'; @@ -51,7 +51,8 @@ test('returns actions set on trigger', () => { const list1 = start.getTriggerActions('trigger' as TriggerId); expect(list1).toHaveLength(1); - expect(list1).toEqual([action1]); + expect(list1[0]).toBeInstanceOf(ActionInternal); + expect(list1[0].id).toBe(action1.id); setup.attachAction('trigger' as TriggerId, action2); const list2 = start.getTriggerActions('trigger' as TriggerId); diff --git a/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx new file mode 100644 index 0000000000000..93d8098a7e294 --- /dev/null +++ b/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiFormRow, EuiFieldText, EuiSwitch } from '@elastic/eui'; +import { ActionDefinition } from '../../actions'; +import { CollectConfigProps } from '../../util'; +import { reactToUiComponent } from '../../../../kibana_react/public'; + +export const SAMPLE_GO_TO_URL_ACTION = 'SAMPLE_GO_TO_URL_ACTION' as ActionDefinition['type']; + +interface Config { + url: string; + openInNewTab: boolean; +} + +const CollectConfig: React.FC> = ({ config, onConfig }) => { + return ( + <> + + onConfig({ ...config, url: event.target.value })} + /> + + + onConfig({ ...config, openInNewTab: !config.openInNewTab })} + /> + + + ); +}; + +export const createSampleGoToUrlAction = (): ActionDefinition => { + return { + type: SAMPLE_GO_TO_URL_ACTION, + id: SAMPLE_GO_TO_URL_ACTION as string, + async execute() {}, + defaultConfig: { + url: '', + openInNewTab: false, + }, + CollectConfig: reactToUiComponent(CollectConfig), + }; +}; diff --git a/src/plugins/ui_actions/public/tests/test_samples/index.ts b/src/plugins/ui_actions/public/tests/test_samples/index.ts index 7d63b1b6d5669..312ba2353a2a5 100644 --- a/src/plugins/ui_actions/public/tests/test_samples/index.ts +++ b/src/plugins/ui_actions/public/tests/test_samples/index.ts @@ -16,4 +16,6 @@ * specific language governing permissions and limitations * under the License. */ + export { createHelloWorldAction } from './hello_world_action'; +export * from './go_to_url_action'; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index d443ce0e592cb..51990d8158d7a 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -17,11 +17,11 @@ * under the License. */ -import { ActionByType } from './actions/action'; +import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; export type TriggerRegistry = Map>; -export type ActionRegistry = Map>; +export type ActionRegistry = Map>; export type TriggerToActionsRegistry = Map; const DEFAULT_TRIGGER = ''; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts new file mode 100644 index 0000000000000..2d89443dd65de --- /dev/null +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UiComponent } from 'src/plugins/kibana_utils/common'; + +/** + * Represents something that can be configured by user using UI. + */ +export interface Configurable { + /** + * Default config for this item, used when item is created for the first time. + */ + readonly defaultConfig?: Config; + + /** + * `UiComponent` to be rendered when collecting configuration for this item. + */ + readonly CollectConfig?: UiComponent>; +} + +/** + * Props provided to `CollectConfig` component on every re-render. + */ +export interface CollectConfigProps { + /** + * Context represents environment where this component is being rendered. + */ + context: Context; + + /** + * Current (latest) config of the item. + */ + config: Config; + + /** + * Callback called when user updates the config in UI. + */ + onConfig: (config: Config) => void; +} diff --git a/src/plugins/ui_actions/public/util/index.ts b/src/plugins/ui_actions/public/util/index.ts new file mode 100644 index 0000000000000..0eb3340993f5f --- /dev/null +++ b/src/plugins/ui_actions/public/util/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './configurable'; +export * from './presentable'; diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts new file mode 100644 index 0000000000000..fbd39bc6f1f90 --- /dev/null +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UiComponent } from 'src/plugins/kibana_utils/common'; + +/** + * Represents something that can be displayed to user in UI. + */ +export interface Presentable { + /** + * ID that uniquely identifies this object. + */ + readonly id: string; + + /** + * Determines the display order in relation to other items. Higher numbers are + * displayed first. + */ + readonly order: number; + + /** + * `UiComponent` to render when displaying this entity as a context menu item. + * If not provided, `getDisplayName` will be used instead. + */ + readonly MenuItem?: UiComponent<{ context: Context }>; + + /** + * Optional EUI icon type that can be displayed along with the title. + */ + getIconType(context: Context): string | undefined; + + /** + * Returns a title to be displayed to the user. + */ + getDisplayName(context: Context): string; + + /** + * Returns a promise that resolves to true if this item is compatible given + * the context and should be displayed to user, otherwise resolves to false. + */ + isCompatible(context: Context): Promise; +} diff --git a/src/plugins/ui_actions/scripts/storybook.js b/src/plugins/ui_actions/scripts/storybook.js new file mode 100644 index 0000000000000..cb2eda610170d --- /dev/null +++ b/src/plugins/ui_actions/scripts/storybook.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'ui_actions', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], +}); From 43583c2d362802431fa9aa37994d4628f4611a08 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 9 Mar 2020 12:09:00 +0100 Subject: [PATCH 03/79] Drilldown context menu (#59638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 fix TypeScript error * feat: 🎸 add CONTEXT_MENU_DRILLDOWNS_TRIGGER trigger * fix: 🐛 correctly order context menu items * fix: 🐛 set correct order on drilldown flyout actions * fix: 🐛 clean up context menu building functions * feat: 🎸 add context menu separator action --- src/plugins/embeddable/public/bootstrap.ts | 4 ++ src/plugins/embeddable/public/index.ts | 1 + .../public/lib/panel/embeddable_panel.tsx | 47 ++++++++++++----- .../public/lib/triggers/triggers.ts | 7 +++ .../build_eui_context_menu_panels.tsx | 50 +++++++++++-------- .../ui_actions/public/context_menu/index.ts | 5 +- src/plugins/ui_actions/public/index.ts | 2 +- .../actions/flyout_create_drilldown/index.tsx | 2 +- .../actions/flyout_edit_drilldown/index.tsx | 6 +-- .../public/service/drilldown_service.ts | 6 +-- 10 files changed, 89 insertions(+), 41 deletions(-) diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index e69361178eeba..dc8d1f27c9136 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -21,6 +21,7 @@ import { Filter } from '../../data/public'; import { applyFilterTrigger, contextMenuTrigger, + contextMenuDrilldownsTrigger, createFilterAction, panelBadgeTrigger, selectRangeTrigger, @@ -32,6 +33,7 @@ import { VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, CONTEXT_MENU_TRIGGER, + CONTEXT_MENU_DRILLDOWNS_TRIGGER, PANEL_BADGE_TRIGGER, ACTION_ADD_PANEL, ACTION_CUSTOMIZE_PANEL, @@ -51,6 +53,7 @@ declare module '../../ui_actions/public' { filters: Filter[]; }; [CONTEXT_MENU_TRIGGER]: EmbeddableContext; + [CONTEXT_MENU_DRILLDOWNS_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; } @@ -70,6 +73,7 @@ declare module '../../ui_actions/public' { */ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); + uiActions.registerTrigger(contextMenuDrilldownsTrigger); uiActions.registerTrigger(applyFilterTrigger); uiActions.registerTrigger(panelBadgeTrigger); uiActions.registerTrigger(selectRangeTrigger); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 0b5fd8184deb1..16622cc2105b9 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -33,6 +33,7 @@ export { ContainerInput, ContainerOutput, CONTEXT_MENU_TRIGGER, + CONTEXT_MENU_DRILLDOWNS_TRIGGER, contextMenuTrigger, ACTION_EDIT_PANEL, EditPanelAction, diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index fc460fbcc17e9..d27dbc14a86d8 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -20,12 +20,22 @@ import { EuiContextMenuPanelDescriptor, EuiPanel, htmlIdGenerator } from '@elast import classNames from 'classnames'; import React from 'react'; import { Subscription } from 'rxjs'; -import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions'; +import { + buildContextMenuForActions, + UiActionsService, + Action, + contextMenuSeparatorAction, +} from '../ui_actions'; import { CoreStart, OverlayStart } from '../../../../../core/public'; import { toMountPoint } from '../../../../kibana_react/public'; import { Start as InspectorStartContract } from '../inspector'; -import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, EmbeddableContext } from '../triggers'; +import { + CONTEXT_MENU_TRIGGER, + CONTEXT_MENU_DRILLDOWNS_TRIGGER, + PANEL_BADGE_TRIGGER, + EmbeddableContext, +} from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../types'; @@ -37,6 +47,14 @@ import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_a import { EditPanelAction } from '../actions'; import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal'; +const sortByOrderField = ( + { order: orderA }: { order?: number }, + { order: orderB }: { order?: number } +) => (orderB || 0) - (orderA || 0); + +const removeById = (disabledActions: string[]) => ({ id }: { id: string }) => + disabledActions.indexOf(id) === -1; + interface Props { embeddable: IEmbeddable; getActions: UiActionsService['getTriggerCompatibleActions']; @@ -200,13 +218,18 @@ export class EmbeddablePanel extends React.Component { }; private getActionContextMenuPanel = async () => { - let actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { + let regularActions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { + embeddable: this.props.embeddable, + }); + let drilldownActions = await this.props.getActions(CONTEXT_MENU_DRILLDOWNS_TRIGGER, { embeddable: this.props.embeddable, }); const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { - actions = actions.filter(action => disabledActions.indexOf(action.id) === -1); + const removeDisabledActions = removeById(disabledActions); + regularActions = regularActions.filter(removeDisabledActions); + drilldownActions = drilldownActions.filter(removeDisabledActions); } const createGetUserData = (overlays: OverlayStart) => @@ -245,16 +268,16 @@ export class EmbeddablePanel extends React.Component { new EditPanelAction(this.props.getEmbeddableFactory), ]; - const sorted = (actions as Array>) - .concat(extraActions) - .sort((a: Action, b: Action) => { - const bOrder = b.order || 0; - const aOrder = a.order || 0; - return bOrder - aOrder; - }); + const sortedRegularActions = [...regularActions, ...extraActions].sort(sortByOrderField); + const sortedDrilldownActions = [...drilldownActions].sort(sortByOrderField); + const actions = [ + ...sortedDrilldownActions, + ...(sortedDrilldownActions.length ? [contextMenuSeparatorAction] : []), + ...sortedRegularActions, + ]; return await buildContextMenuForActions({ - actions: sorted, + actions, actionContext: { embeddable: this.props.embeddable }, closeMenu: this.closeMyContextMenuPanel, }); diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index a348e1ed79d8d..6fc78e46eb23a 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -54,6 +54,13 @@ export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { description: 'Triggered on top-right corner context-menu select.', }; +export const CONTEXT_MENU_DRILLDOWNS_TRIGGER = 'CONTEXT_MENU_DRILLDOWNS_TRIGGER'; +export const contextMenuDrilldownsTrigger: Trigger<'CONTEXT_MENU_DRILLDOWNS_TRIGGER'> = { + id: CONTEXT_MENU_DRILLDOWNS_TRIGGER, + title: 'Drilldown context menu', + description: 'Triggered on top-right corner context-menu select.', +}; + export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'> = { id: APPLY_FILTER_TRIGGER, diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 327f0ec15137f..98c0e32713d74 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -21,8 +21,23 @@ import * as React from 'react'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { uiToReactComponent } from '../../../kibana_react/public'; -import { Action } from '../actions'; +import { uiToReactComponent, reactToUiComponent } from '../../../kibana_react/public'; +import { Action, ActionInternal } from '../actions'; + +export const contextMenuSeparatorAction = new ActionInternal({ + id: 'CONTEXT_MENU_SEPARATOR', + getDisplayName: () => 'separator', + MenuItem: reactToUiComponent(() => ( +
+ )), + execute: () => Promise.resolve(), +}); /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. @@ -63,33 +78,25 @@ async function buildEuiContextMenuPanelItems({ actionContext: Context; closeMenu: () => void; }) { - const items: EuiContextMenuPanelItemDescriptor[] = []; - const promises = actions.map(async action => { + const items: EuiContextMenuPanelItemDescriptor[] = new Array(actions.length); + const promises = actions.map(async (action, index) => { const isCompatible = await action.isCompatible(actionContext); if (!isCompatible) { return; } - items.push( - convertPanelActionToContextMenuItem({ - action, - actionContext, - closeMenu, - }) - ); + items[index] = convertPanelActionToContextMenuItem({ + action, + actionContext, + closeMenu, + }); }); await Promise.all(promises); - return items; + return items.filter(Boolean); } -/** - * - * @param {ContextMenuAction} action - * @param {Embeddable} embeddable - * @return {EuiContextMenuPanelItemDescriptor} - */ function convertPanelActionToContextMenuItem({ action, actionContext, @@ -115,8 +122,11 @@ function convertPanelActionToContextMenuItem({ closeMenu(); }; - if (action.getHref && action.getHref(actionContext)) { - menuPanelItem.href = action.getHref(actionContext); + if (action.getHref) { + const href = action.getHref(actionContext); + if (href) { + menuPanelItem.href = action.getHref(actionContext); + } } return menuPanelItem; diff --git a/src/plugins/ui_actions/public/context_menu/index.ts b/src/plugins/ui_actions/public/context_menu/index.ts index aa8df8b6965d8..96df8f7601b0b 100644 --- a/src/plugins/ui_actions/public/context_menu/index.ts +++ b/src/plugins/ui_actions/public/context_menu/index.ts @@ -17,5 +17,8 @@ * under the License. */ -export { buildContextMenuForActions } from './build_eui_context_menu_panels'; +export { + buildContextMenuForActions, + contextMenuSeparatorAction, +} from './build_eui_context_menu_panels'; export { openContextMenu } from './open_context_menu'; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 1844388b91884..2ac34f6795dc4 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,7 +35,7 @@ export { ActionContract as UiActionsActionContract, } from './actions'; export { CollectConfigProps as UiActionsCollectConfigProps } from './util'; -export { buildContextMenuForActions } from './context_menu'; +export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx index 0ce10b2a801e3..a54648924de22 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx @@ -25,7 +25,7 @@ export interface OpenFlyoutAddDrilldownParams { export class FlyoutCreateDrilldownAction implements ActionByType { public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; - public order = 100; + public order = 2; constructor(protected readonly params: OpenFlyoutAddDrilldownParams) {} diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx index f109da94fcaca..e5990f6157bc6 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx @@ -14,7 +14,7 @@ import { reactToUiComponent, } from '../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FormCreateDrilldown } from '../../components/form_create_drilldown'; +import { FormDrilldownWizard } from '../../components/form_drilldown_wizard'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -36,7 +36,7 @@ const drilldrownCount = 2; export class FlyoutEditDrilldownAction implements ActionByType { public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; - public order = 100; + public order = 1; constructor(protected readonly params: FlyoutEditDrilldownParams) {} @@ -67,6 +67,6 @@ export class FlyoutEditDrilldownAction implements ActionByType)); + overlays.openFlyout(toMountPoint()); } } diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts index 7209045191e94..131b9dc9b9160 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -5,7 +5,7 @@ */ import { CoreSetup } from 'src/core/public'; -import { CONTEXT_MENU_TRIGGER } from '../../../../../src/plugins/embeddable/public'; +import { CONTEXT_MENU_DRILLDOWNS_TRIGGER } from '../../../../../src/plugins/embeddable/public'; import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction } from '../actions'; import { DrilldownsSetupDependencies } from '../plugin'; @@ -15,11 +15,11 @@ export class DrilldownService { const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays }); uiActions.registerAction(actionFlyoutCreateDrilldown); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); + uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays }); uiActions.registerAction(actionFlyoutEditDrilldown); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); + uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); } /** From e1452412e2263b25ffd5ba9fb454b7b5548ece5a Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 9 Mar 2020 12:58:19 +0100 Subject: [PATCH 04/79] Add basic ActionFactoryService. Pass data from it into components instead of mocks --- .../ui_actions/public/actions/action.ts | 6 +- .../public/actions/action_internal.ts | 19 +- src/plugins/ui_actions/public/index.ts | 3 +- .../ui_actions/public/util/configurable.ts | 23 ++- .../ui_actions/public/util/presentable.ts | 6 +- .../action_wizard/action_wizard.tsx | 38 +--- .../public/components/action_wizard/index.ts | 2 +- .../components/action_wizard/test_data.tsx | 194 ++++++++++-------- .../advanced_ui_actions/public/index.ts | 2 + .../advanced_ui_actions/public/plugin.ts | 30 ++- .../public/ui_actions_factory/index.ts | 7 + .../ui_actions_factory_service.ts | 42 ++++ .../actions/flyout_create_drilldown/index.tsx | 17 +- .../actions/flyout_edit_drilldown/index.tsx | 35 +++- .../flyout_drilldown_wizard.story.tsx | 19 +- .../flyout_drilldown_wizard.tsx | 12 +- .../flyout_manage_drilldowns.story.tsx | 10 +- .../flyout_manage_drilldowns.tsx | 5 + x-pack/plugins/drilldowns/public/plugin.ts | 3 + .../public/service/drilldown_service.ts | 22 +- 20 files changed, 314 insertions(+), 181 deletions(-) create mode 100644 x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 789542766f5f5..119a152dba728 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -20,7 +20,6 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; import { ActionType, ActionContextMapping } from '../types'; import { Presentable } from '../util/presentable'; -import { Configurable } from '../util/configurable'; export type ActionByType = Action; @@ -82,7 +81,7 @@ export interface Action export interface ActionDefinition< Context extends object = object, Config extends object | undefined = undefined -> extends Partial>, Partial> { +> extends Partial> { /** * ID of the action that uniquely identifies this action in the actions registry. */ @@ -116,7 +115,6 @@ export type ActionConfig = A extends ActionDefinition ? Co export type DynamicActionDefinition< Context extends object = object, Config extends object | undefined = undefined -> = ActionDefinition & - Required, 'CollectConfig' | 'defaultConfig' | 'type'>>; +> = ActionDefinition; export type AnyDynamicActionDefinition = DynamicActionDefinition; diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index 0ff139ff7ae4f..0edd5e3eaa602 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -19,7 +19,7 @@ import { Action, ActionContext, AnyActionDefinition } from './action'; import { Presentable } from '../util/presentable'; -import { createActionStateContainer, ActionState } from './action_state_container'; +// import { ActionState } from './action_state_container'; import { uiToReactComponent } from '../../../kibana_react/public'; import { ActionContract } from './action_contract'; import { ActionType } from '../types'; @@ -33,20 +33,11 @@ export class ActionInternal public readonly order: number = this.definition.order || 0; public readonly MenuItem? = this.definition.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - public readonly CollectConfig? = this.definition.CollectConfig; - public readonly ReactCollectConfig? = this.CollectConfig - ? uiToReactComponent(this.CollectConfig) - : undefined; public get contract(): ActionContract { return this; } - public readonly state = createActionStateContainer({ - name: '', - config: this.definition.defaultConfig || {}, - }); - public execute(context: ActionContext) { return this.definition.execute(context); } @@ -72,19 +63,15 @@ export class ActionInternal } serialize(): SerializedAction { - const state = this.state.get(); const serialized: SerializedAction = { id: this.id, type: this.type || '', - state, }; return serialized; } - deserialize({ state }: SerializedAction) { - this.state.set(state); - } + deserialize() {} } export type AnyActionInternal = ActionInternal; @@ -92,5 +79,5 @@ export type AnyActionInternal = ActionInternal; export interface SerializedAction { readonly id: string; readonly type: string; - readonly state: ActionState; + // readonly state: ActionState; } diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 2ac34f6795dc4..88ad8f5d6386e 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -34,8 +34,9 @@ export { ActionInternal as UiActionsActionInternal, ActionContract as UiActionsActionContract, } from './actions'; -export { CollectConfigProps as UiActionsCollectConfigProps } from './util'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; +export { CollectConfigProps, Presentable, Configurable, ConfigurableBaseConfig } from './util'; +export { buildContextMenuForActions } from './context_menu'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index 2d89443dd65de..4daa601e93779 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -19,30 +19,33 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigurableBaseConfig {} + /** * Represents something that can be configured by user using UI. */ -export interface Configurable { +export interface Configurable { + /** + * Create default config for this item, used when item is created for the first time. + */ + readonly createConfig: () => Config; + /** - * Default config for this item, used when item is created for the first time. + * Is this config valid. Used to validate user's input before saving */ - readonly defaultConfig?: Config; + readonly isConfigValid: (config: Config) => boolean; /** * `UiComponent` to be rendered when collecting configuration for this item. */ - readonly CollectConfig?: UiComponent>; + readonly CollectConfig: UiComponent>; } /** * Props provided to `CollectConfig` component on every re-render. */ -export interface CollectConfigProps { - /** - * Context represents environment where this component is being rendered. - */ - context: Context; - +export interface CollectConfigProps { /** * Current (latest) config of the item. */ diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index fbd39bc6f1f90..464802eccd0b6 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -43,16 +43,16 @@ export interface Presentable { /** * Optional EUI icon type that can be displayed along with the title. */ - getIconType(context: Context): string | undefined; + getIconType(context?: Context): string | undefined; /** * Returns a title to be displayed to the user. */ - getDisplayName(context: Context): string; + getDisplayName(context?: Context): string; /** * Returns a promise that resolves to true if this item is compatible given * the context and should be displayed to user, otherwise resolves to false. */ - isCompatible(context: Context): Promise; + isCompatible(context?: Context): Promise; } diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 41ef863c00e44..930ec7e15cb01 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -16,28 +16,8 @@ import { } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; - -// TODO: this interface is temporary for just moving forward with the component -// and it will be imported from the ../ui_actions when implemented properly -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type ActionBaseConfig = {}; -export interface ActionFactory { - type: string; // TODO: type should be tied to Action and ActionByType - displayName: string; - iconType?: string; - wizard: React.FC>; - createConfig: () => Config; - isValid: (config: Config) => boolean; -} - -export interface ActionFactoryWizardProps { - config?: Config; - - /** - * Callback called when user updates the config in UI. - */ - onConfig: (config: Config) => void; -} +import { ActionBaseConfig, ActionFactory } from '../../ui_actions_factory'; +import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; export interface ActionWizardProps { /** @@ -130,14 +110,14 @@ const SelectedActionFactory: React.FC = ({ >
- {actionFactory.iconType && ( + {actionFactory.getIconType() && ( - + )} -

{actionFactory.displayName}

+

{actionFactory.getDisplayName()}

{showDeselect && ( @@ -151,7 +131,7 @@ const SelectedActionFactory: React.FC = ({
- {actionFactory.wizard({ + {uiToReactComponent(actionFactory.CollectConfig)({ config, onConfig: onConfigChange, })} @@ -182,13 +162,13 @@ const ActionFactorySelector: React.FC = ({ {actionFactories.map(actionFactory => ( onActionFactorySelected(actionFactory)} > - {actionFactory.iconType && } + {actionFactory.getIconType() && } ))} diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts index 3e7d0bf79bdc3..a189afbf956ee 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ActionFactory, ActionWizard, ActionBaseConfig } from './action_wizard'; +export { ActionWizard } from './action_wizard'; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 8ecdde681069e..d08bd299dddd8 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -6,21 +6,75 @@ import React, { useState } from 'react'; import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; -import { ActionFactory, ActionBaseConfig, ActionWizard } from './action_wizard'; +import { ActionWizard } from './action_wizard'; +import { ActionBaseConfig, ActionFactory } from '../../ui_actions_factory'; +import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; +import { CollectConfigProps } from '../../../../../../src/plugins/ui_actions/public'; export const dashboards = [ { id: 'dashboard1', title: 'Dashboard 1' }, { id: 'dashboard2', title: 'Dashboard 2' }, ]; -export const dashboardDrilldownActionFactory: ActionFactory<{ +interface DashboardDrilldownConfig { dashboardId?: string; useCurrentDashboardFilters: boolean; useCurrentDashboardDataRange: boolean; -}> = { - type: 'Dashboard', - displayName: 'Go to Dashboard', - iconType: 'dashboardApp', +} + +function DashboardDrilldownCollectConfig(props: CollectConfigProps) { + const config = props.config ?? { + dashboardId: undefined, + useCurrentDashboardDataRange: true, + useCurrentDashboardFilters: true, + }; + return ( + <> + + ({ value: id, text: title }))} + value={config.dashboardId} + onChange={e => { + props.onConfig({ ...config, dashboardId: e.target.value }); + }} + /> + + + + props.onConfig({ + ...config, + useCurrentDashboardFilters: !config.useCurrentDashboardFilters, + }) + } + /> + + + + props.onConfig({ + ...config, + useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, + }) + } + /> + + + ); +} + +export const dashboardDrilldownActionFactory: ActionFactory = { + id: 'Dashboard', + getDisplayName: () => 'Go to Dashboard', + getIconType: () => 'dashboardApp', createConfig: () => { return { dashboardId: undefined, @@ -28,99 +82,67 @@ export const dashboardDrilldownActionFactory: ActionFactory<{ useCurrentDashboardFilters: true, }; }, - isValid: config => { + isConfigValid: config => { if (!config.dashboardId) return false; return true; }, - wizard: props => { - const config = props.config ?? { - dashboardId: undefined, - useCurrentDashboardDataRange: true, - useCurrentDashboardFilters: true, - }; - return ( - <> - - ({ value: id, text: title }))} - value={config.dashboardId} - onChange={e => { - props.onConfig({ ...config, dashboardId: e.target.value }); - }} - /> - - - - props.onConfig({ - ...config, - useCurrentDashboardFilters: !config.useCurrentDashboardFilters, - }) - } - /> - - - - props.onConfig({ - ...config, - useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, - }) - } - /> - - - ); + CollectConfig: reactToUiComponent(DashboardDrilldownCollectConfig), + + isCompatible(context?: object): Promise { + return Promise.resolve(true); }, + order: 0, }; -export const urlDrilldownActionFactory: ActionFactory<{ url: string; openInNewTab: boolean }> = { - type: 'Url', - displayName: 'Go to URL', - iconType: 'link', +interface UrlDrilldownConfig { + url: string; + openInNewTab: boolean; +} +function UrlDrilldownCollectConfig(props: CollectConfigProps) { + const config = props.config ?? { + url: '', + openInNewTab: false, + }; + return ( + <> + + props.onConfig({ ...config, url: event.target.value })} + /> + + + props.onConfig({ ...config, openInNewTab: !config.openInNewTab })} + /> + + + ); +} +export const urlDrilldownActionFactory: ActionFactory = { + id: 'Url', + getDisplayName: () => 'Go to URL', + getIconType: () => 'link', createConfig: () => { return { url: '', openInNewTab: false, }; }, - isValid: config => { + isConfigValid: config => { if (!config.url) return false; return true; }, - wizard: props => { - const config = props.config ?? { - url: '', - openInNewTab: false, - }; - return ( - <> - - props.onConfig({ ...config, url: event.target.value })} - /> - - - props.onConfig({ ...config, openInNewTab: !config.openInNewTab })} - /> - - - ); + CollectConfig: reactToUiComponent(UrlDrilldownCollectConfig), + + order: 10, + isCompatible(context?: object): Promise { + return Promise.resolve(true); }, }; @@ -160,11 +182,11 @@ export function Demo({ actionFactories }: { actionFactories: Array

-
Action Factory Type: {state.currentActionFactory?.type}
+
Action Factory Id: {state.currentActionFactory?.id}
Action Factory Config: {JSON.stringify(state.config)}
Is config valid:{' '} - {JSON.stringify(state.currentActionFactory?.isValid(state.config!) ?? false)} + {JSON.stringify(state.currentActionFactory?.isConfigValid(state.config!) ?? false)}
); diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 0619c12b9b40e..abecbf354734c 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -12,5 +12,7 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { AdvancedUiActionsPublicPlugin as Plugin }; +export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; export * from './components'; +export * from './ui_actions_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 2f6935cdf1961..73592cb18cebf 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -30,6 +30,7 @@ import { TimeBadgeActionContext, } from './custom_time_range_badge'; import { CommonlyUsedRange } from './types'; +import { UiActionsFactoryService } from './ui_actions_factory'; interface SetupDependencies { embeddable: IEmbeddableSetup; // Embeddable are needed because they register basic triggers/actions. @@ -41,8 +42,12 @@ interface StartDependencies { uiActions: UiActionsStart; } -export type Setup = void; -export type Start = void; +export interface AdvancedUiActionsSetup { + actionFactory: Pick; +} +export interface AdvancedUiActionsStart { + actionFactory: Pick; +} declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -52,12 +57,19 @@ declare module '../../../../src/plugins/ui_actions/public' { } export class AdvancedUiActionsPublicPlugin - implements Plugin { + implements + Plugin { + private readonly actionFactoryService = new UiActionsFactoryService(); + constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { uiActions }: SetupDependencies): Setup {} + public setup(core: CoreSetup, { uiActions }: SetupDependencies): AdvancedUiActionsSetup { + return { + actionFactory: this.actionFactoryService, + }; + } - public start(core: CoreStart, { uiActions }: StartDependencies): Start { + public start(core: CoreStart, { uiActions }: StartDependencies): AdvancedUiActionsStart { const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; const { openModal } = createReactOverlays(core); @@ -76,7 +88,13 @@ export class AdvancedUiActionsPublicPlugin }); uiActions.registerAction(timeRangeBadge); uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge); + + return { + actionFactory: this.actionFactoryService, + }; } - public stop() {} + public stop() { + this.actionFactoryService.clear(); + } } diff --git a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts new file mode 100644 index 0000000000000..be0b69d483037 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './ui_actions_factory_service'; diff --git a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts new file mode 100644 index 0000000000000..5c9879f11b2f5 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Presentable, + Configurable, + ConfigurableBaseConfig, +} from '../../../../../src/plugins/ui_actions/public'; + +export type ActionBaseConfig = ConfigurableBaseConfig; +export interface ActionFactory + extends Presentable, + Configurable {} + +type ActionFactoryRegistry = Map>; + +export class UiActionsFactoryService { + protected readonly actionFactories: ActionFactoryRegistry; + + constructor({ actionFactories = new Map() }: { actionFactories?: ActionFactoryRegistry } = {}) { + this.actionFactories = actionFactories; + } + + public readonly register = (actionFactory: ActionFactory) => { + if (this.actionFactories.has(actionFactory.id)) { + throw new Error(`ActionFactory [actionFactory.id = ${actionFactory.id}] already registered.`); + } + + this.actionFactories.set(actionFactory.id, actionFactory); + }; + + public readonly getAll = (): Array> => { + return Array.from(this.actionFactories.values()); + }; + + public readonly clear = () => { + this.actionFactories.clear(); + }; +} diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx index a54648924de22..f0af6d2c9545e 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx @@ -11,6 +11,7 @@ import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; import { FlyoutDrilldownWizard } from '../../components/flyout_drilldown_wizard'; +import { ActionFactory } from '../../../../advanced_ui_actions/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -20,6 +21,7 @@ export interface FlyoutCreateDrilldownActionContext { export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; + getDrilldownActionFactories: () => Array>; } export class FlyoutCreateDrilldownAction implements ActionByType { @@ -45,8 +47,21 @@ export class FlyoutCreateDrilldownAction implements ActionByType factory.isCompatible(context)) + ).then(compatibilityList => + drilldownActionFactories.filter((factory, index) => compatibilityList[index]) + ); + const handle = overlays.openFlyout( - toMountPoint( handle.close()} />) + toMountPoint( + handle.close()} + drilldownActionFactories={compatibleDrilldownActionFactories} + /> + ) ); } } diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx index e5990f6157bc6..d8f290ac9f4ba 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx @@ -10,11 +10,14 @@ import { CoreStart } from 'src/core/public'; import { EuiNotificationBadge } from '@elastic/eui'; import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; import { - toMountPoint, reactToUiComponent, + toMountPoint, } from '../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FormDrilldownWizard } from '../../components/form_drilldown_wizard'; +import { FlyoutManageDrilldowns } from '../../components/flyout_manage_drilldowns'; +// TODO: MOCK DATA +import { drilldowns } from '../../components/list_manage_drilldowns/test_data'; +import { ActionFactory } from '../../../../advanced_ui_actions/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -24,15 +27,13 @@ export interface FlyoutEditDrilldownActionContext { export interface FlyoutEditDrilldownParams { overlays: () => Promise; + getDrilldownActionFactories: () => Array>; } const displayName = i18n.translate('xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName', { defaultMessage: 'Manage drilldowns', }); -// mocked data -const drilldrownCount = 2; - export class FlyoutEditDrilldownAction implements ActionByType { public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; @@ -53,7 +54,7 @@ export class FlyoutEditDrilldownAction implements ActionByType {displayName}{' '} - {drilldrownCount} + {drilldowns.length} ); @@ -62,11 +63,27 @@ export class FlyoutEditDrilldownAction implements ActionByType 0; + return embeddable.getInput().viewMode === 'edit' && drilldowns.length > 0; } - public async execute({ embeddable }: FlyoutEditDrilldownActionContext) { + public async execute(context: FlyoutEditDrilldownActionContext) { const overlays = await this.params.overlays(); - overlays.openFlyout(toMountPoint()); + + const drilldownActionFactories = this.params.getDrilldownActionFactories(); + const compatibleDrilldownActionFactories = await Promise.all( + drilldownActionFactories.map(factory => factory.isCompatible(context)) + ).then(compatibilityList => + drilldownActionFactories.filter((factory, index) => compatibilityList[index]) + ); + + const handle = overlays.openFlyout( + toMountPoint( + handle.close()} + drilldowns={drilldowns} + drilldownActionFactories={compatibleDrilldownActionFactories} + /> + ) + ); } } diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx index a6395db54560c..f61fbfbb54445 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -10,17 +10,27 @@ import * as React from 'react'; import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; import { FlyoutDrilldownWizard } from '.'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { urlDrilldownActionFactory } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +import { + urlDrilldownActionFactory, + dashboardDrilldownActionFactory, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; storiesOf('components/FlyoutDrilldownWizard', module) .add('default', () => { - return ; + return ( + + ); }) .add('open in flyout - create', () => { return ( {}}> - {}} /> + {}} + drilldownActionFactories={[urlDrilldownActionFactory, dashboardDrilldownActionFactory]} + /> ); }) @@ -29,6 +39,7 @@ storiesOf('components/FlyoutDrilldownWizard', module) {}}> {}} + drilldownActionFactories={[urlDrilldownActionFactory, dashboardDrilldownActionFactory]} initialDrilldownWizardConfig={{ name: 'My fancy drilldown', actionFactory: urlDrilldownActionFactory, diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index fcc5bcf2fa8b8..d8d7e253bb81a 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -16,11 +16,6 @@ import { txtEditDrilldownTitle, } from './i18n'; import { ActionBaseConfig, ActionFactory } from '../../../../advanced_ui_actions/public'; -import { - dashboardDrilldownActionFactory, - urlDrilldownActionFactory, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; import { DrilldownHelloBar } from '../drilldown_hello_bar'; export interface DrilldownWizardConfig { @@ -32,6 +27,8 @@ export interface DrilldownWizardConfig { + drilldownActionFactories: Array>; + onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void; onDelete?: () => void; onClose?: () => void; @@ -55,6 +52,7 @@ export function FlyoutDrilldownWizard< onDelete = () => {}, showWelcomeMessage = false, onWelcomeHideClick, + drilldownActionFactories, }: FlyoutDrilldownWizardProps) { const [wizardConfig, setWizardConfig] = useState( () => @@ -68,7 +66,7 @@ export function FlyoutDrilldownWizard< if (!wizardConfig.actionFactory) return false; if (!wizardConfig.actionConfig) return false; - return wizardConfig.actionFactory.isValid(wizardConfig.actionConfig); + return wizardConfig.actionFactory.isConfigValid(wizardConfig.actionConfig); }; const footer = ( @@ -124,7 +122,7 @@ export function FlyoutDrilldownWizard< }); } }} - actionFactories={[dashboardDrilldownActionFactory, urlDrilldownActionFactory]} + actionFactories={drilldownActionFactories} /> {mode === 'edit' && ( <> diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx index 33feea11c4f29..4fbe874f01561 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx @@ -9,9 +9,17 @@ import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; import { FlyoutManageDrilldowns } from './flyout_manage_drilldowns'; import { drilldowns } from '../list_manage_drilldowns/test_data'; +import { + dashboardDrilldownActionFactory, + urlDrilldownActionFactory, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx index 207cb3e42d2b3..152b49f62fddd 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx @@ -8,9 +8,12 @@ import React, { useState } from 'react'; import { DrilldownListItem } from '../list_manage_drilldowns'; import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; +import { ActionFactory } from '../../../../advanced_ui_actions/public'; export interface FlyoutManageDrilldownsProps { drilldowns: DrilldownListItem[]; + drilldownActionFactories: Array>; + onClose?: () => void; showWelcomeMessage?: boolean; onHideWelcomeMessage?: () => void; @@ -27,6 +30,7 @@ export function FlyoutManageDrilldowns({ onClose = () => {}, showWelcomeMessage = true, onHideWelcomeMessage, + drilldownActionFactories, }: FlyoutManageDrilldownsProps) { const [viewState, setViewState] = useState(ViewState.List); @@ -37,6 +41,7 @@ export function FlyoutManageDrilldowns({ case ViewState.Edit: return ( setViewState(ViewState.List)} onDelete={() => { diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index b89172541b91e..ab7f5cc0f52f6 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -13,13 +13,16 @@ import { OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; export interface DrilldownsSetupDependencies { uiActions: UiActionsSetup; + advancedUiActions: AdvancedUiActionsSetup; } export interface DrilldownsStartDependencies { uiActions: UiActionsStart; + advancedUiActions: AdvancedUiActionsStart; } export type DrilldownsSetupContract = Pick; diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts index 131b9dc9b9160..4149bfada84d1 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -8,18 +8,34 @@ import { CoreSetup } from 'src/core/public'; import { CONTEXT_MENU_DRILLDOWNS_TRIGGER } from '../../../../../src/plugins/embeddable/public'; import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction } from '../actions'; import { DrilldownsSetupDependencies } from '../plugin'; +// TODO: MOCK DATA +import { + dashboardDrilldownActionFactory, + urlDrilldownActionFactory, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../advanced_ui_actions/public/components/action_wizard/test_data'; export class DrilldownService { - bootstrap(core: CoreSetup, { uiActions }: DrilldownsSetupDependencies) { + bootstrap(core: CoreSetup, { uiActions, advancedUiActions }: DrilldownsSetupDependencies) { const overlays = async () => (await core.getStartServices())[0].overlays; - const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays }); + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ + overlays, + getDrilldownActionFactories: () => advancedUiActions.actionFactory.getAll(), + }); uiActions.registerAction(actionFlyoutCreateDrilldown); uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); - const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays }); + const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ + overlays, + getDrilldownActionFactories: () => advancedUiActions.actionFactory.getAll(), + }); uiActions.registerAction(actionFlyoutEditDrilldown); uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); + + // TODO: mocks + advancedUiActions.actionFactory.register(dashboardDrilldownActionFactory); + advancedUiActions.actionFactory.register(urlDrilldownActionFactory); } /** From 81e6bcb3472c2148e6481211b6af1083a6ac131e Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 9 Mar 2020 14:46:00 +0100 Subject: [PATCH 05/79] Dashboard x pack (#59653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add dashboard_enhanced plugin to x-pack * feat: 🎸 improve context menu separator * feat: 🎸 move drilldown flyout actions to dashboard_enhanced * fix: 🐛 fix exports from ui_actions plugin --- .github/CODEOWNERS | 1 + .../build_eui_context_menu_panels.tsx | 26 ++++++------ src/plugins/ui_actions/public/index.ts | 3 +- x-pack/plugins/dashboard_enhanced/README.md | 1 + x-pack/plugins/dashboard_enhanced/kibana.json | 7 ++++ .../dashboard_enhanced/public/index.ts | 18 +++++++++ .../dashboard_enhanced/public/mocks.ts | 27 +++++++++++++ .../dashboard_enhanced/public/plugin.ts | 40 +++++++++++++++++++ .../actions/flyout_create_drilldown/index.tsx | 15 +++---- .../actions/flyout_edit_drilldown/index.tsx | 33 +++++---------- .../services/drilldowns}/actions/index.ts | 0 .../dashboard_drilldowns_services.ts | 38 ++++++++++++++++++ .../public/services/drilldowns/index.ts | 7 ++++ .../public/services/index.ts | 7 ++++ x-pack/plugins/drilldowns/public/mocks.ts | 2 +- x-pack/plugins/drilldowns/public/plugin.ts | 16 -------- .../public/service/drilldown_service.ts | 18 --------- 17 files changed, 179 insertions(+), 80 deletions(-) create mode 100644 x-pack/plugins/dashboard_enhanced/README.md create mode 100644 x-pack/plugins/dashboard_enhanced/kibana.json create mode 100644 x-pack/plugins/dashboard_enhanced/public/index.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/mocks.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/plugin.ts rename x-pack/plugins/{drilldowns/public => dashboard_enhanced/public/services/drilldowns}/actions/flyout_create_drilldown/index.tsx (78%) rename x-pack/plugins/{drilldowns/public => dashboard_enhanced/public/services/drilldowns}/actions/flyout_edit_drilldown/index.tsx (63%) rename x-pack/plugins/{drilldowns/public => dashboard_enhanced/public/services/drilldowns}/actions/index.ts (100%) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/index.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index de74a2c42be8b..e18e1c448e406 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,7 @@ # For more info, see https://help.github.com/articles/about-codeowners/ # App +/x-pack/legacy/plugins/dashboard_enhanced/ @elastic/kibana-app /x-pack/legacy/plugins/lens/ @elastic/kibana-app /x-pack/legacy/plugins/graph/ @elastic/kibana-app /src/legacy/server/url_shortening/ @elastic/kibana-app diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 98c0e32713d74..e4c89b6a3bb8a 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -18,7 +18,11 @@ */ import * as React from 'react'; -import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, + EuiHorizontalRule, +} from '@elastic/eui'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { uiToReactComponent, reactToUiComponent } from '../../../kibana_react/public'; @@ -27,15 +31,7 @@ import { Action, ActionInternal } from '../actions'; export const contextMenuSeparatorAction = new ActionInternal({ id: 'CONTEXT_MENU_SEPARATOR', getDisplayName: () => 'separator', - MenuItem: reactToUiComponent(() => ( -
- )), + MenuItem: reactToUiComponent(() => ), execute: () => Promise.resolve(), }); @@ -117,10 +113,12 @@ function convertPanelActionToContextMenuItem({ 'data-test-subj': `embeddablePanelAction-${action.id}`, }; - menuPanelItem.onClick = () => { - action.execute(actionContext); - closeMenu(); - }; + if (action.id !== 'CONTEXT_MENU_SEPARATOR') { + menuPanelItem.onClick = () => { + action.execute(actionContext); + closeMenu(); + }; + } if (action.getHref) { const href = action.getHref(actionContext); diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 88ad8f5d6386e..f52d1e2af9a88 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,8 +35,7 @@ export { ActionContract as UiActionsActionContract, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; -export { CollectConfigProps, Presentable, Configurable, ConfigurableBaseConfig } from './util'; -export { buildContextMenuForActions } from './context_menu'; +export { CollectConfigProps, Presentable, Configurable } from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/x-pack/plugins/dashboard_enhanced/README.md b/x-pack/plugins/dashboard_enhanced/README.md new file mode 100644 index 0000000000000..d9296ae158621 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/README.md @@ -0,0 +1 @@ +# X-Pack part of Dashboard app diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json new file mode 100644 index 0000000000000..e871bee3e4625 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "dashboardEnhanced", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions", "drilldowns"] +} diff --git a/x-pack/plugins/dashboard_enhanced/public/index.ts b/x-pack/plugins/dashboard_enhanced/public/index.ts new file mode 100644 index 0000000000000..8f48bae9f3803 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DashboardEnhancedPlugin } from './plugin'; + +export { + SetupContract as DashboardEnhancedSetupContract, + SetupDependencies as DashboardEnhancedSetupDependencies, + StartContract as DashboardEnhancedStartContract, + StartDependencies as DashboardEnhancedStartDependencies, +} from './plugin'; + +export function plugin() { + return new DashboardEnhancedPlugin(); +} diff --git a/x-pack/plugins/dashboard_enhanced/public/mocks.ts b/x-pack/plugins/dashboard_enhanced/public/mocks.ts new file mode 100644 index 0000000000000..67dc1fd97d521 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/mocks.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DashboardEnhancedSetupContract, DashboardEnhancedStartContract } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = {}; + + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +export const dashboardEnhancedPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts new file mode 100644 index 0000000000000..9943817631db2 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { DashboardDrilldownsService } from './services'; + +export interface SetupDependencies { + uiActions: UiActionsSetup; +} + +export interface StartDependencies { + uiActions: UiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class DashboardEnhancedPlugin + implements Plugin { + public readonly drilldowns = new DashboardDrilldownsService(); + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + this.drilldowns.bootstrap(core, plugins); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx similarity index 78% rename from x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index f0af6d2c9545e..7c98348b307f3 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; -import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; -import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FlyoutDrilldownWizard } from '../../components/flyout_drilldown_wizard'; -import { ActionFactory } from '../../../../advanced_ui_actions/public'; +import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; +import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; +import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; + +const FlyoutDrilldownWizard: React.FC<{ onClose: any }> = () =>
FormDrilldownWizard
; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -21,7 +21,6 @@ export interface FlyoutCreateDrilldownActionContext { export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; - getDrilldownActionFactories: () => Array>; } export class FlyoutCreateDrilldownAction implements ActionByType { @@ -48,18 +47,20 @@ export class FlyoutCreateDrilldownAction implements ActionByType factory.isCompatible(context)) ).then(compatibilityList => drilldownActionFactories.filter((factory, index) => compatibilityList[index]) ); + */ const handle = overlays.openFlyout( toMountPoint( handle.close()} - drilldownActionFactories={compatibleDrilldownActionFactories} + // drilldownActionFactories={compatibleDrilldownActionFactories} /> ) ); diff --git a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx similarity index 63% rename from x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index d8f290ac9f4ba..7bd2030786117 100644 --- a/x-pack/plugins/drilldowns/public/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -8,16 +8,19 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; import { EuiNotificationBadge } from '@elastic/eui'; -import { ActionByType } from '../../../../../../src/plugins/ui_actions/public'; +import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { reactToUiComponent, toMountPoint, -} from '../../../../../../src/plugins/kibana_react/public'; -import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -import { FlyoutManageDrilldowns } from '../../components/flyout_manage_drilldowns'; -// TODO: MOCK DATA -import { drilldowns } from '../../components/list_manage_drilldowns/test_data'; -import { ActionFactory } from '../../../../advanced_ui_actions/public'; +} from '../../../../../../../../src/plugins/kibana_react/public'; +import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; + +const FlyoutManageDrilldowns: React.FC<{ onClose: () => {} }> = () => ( +
FormDrilldownWizard
+); + +// Mock data +const drilldowns: any = []; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -27,7 +30,6 @@ export interface FlyoutEditDrilldownActionContext { export interface FlyoutEditDrilldownParams { overlays: () => Promise; - getDrilldownActionFactories: () => Array>; } const displayName = i18n.translate('xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName', { @@ -69,21 +71,8 @@ export class FlyoutEditDrilldownAction implements ActionByType factory.isCompatible(context)) - ).then(compatibilityList => - drilldownActionFactories.filter((factory, index) => compatibilityList[index]) - ); - const handle = overlays.openFlyout( - toMountPoint( - handle.close()} - drilldowns={drilldowns} - drilldownActionFactories={compatibleDrilldownActionFactories} - /> - ) + toMountPoint( handle.close()} />) ); } } diff --git a/x-pack/plugins/drilldowns/public/actions/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/index.ts similarity index 100% rename from x-pack/plugins/drilldowns/public/actions/index.ts rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/index.ts diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts new file mode 100644 index 0000000000000..fb9164227156a --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'src/core/public'; +import { SetupDependencies } from '../../plugin'; +import { CONTEXT_MENU_DRILLDOWNS_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; +import { + FlyoutCreateDrilldownAction, + FlyoutCreateDrilldownActionContext, + FlyoutEditDrilldownAction, + FlyoutEditDrilldownActionContext, + OPEN_FLYOUT_ADD_DRILLDOWN, + OPEN_FLYOUT_EDIT_DRILLDOWN, +} from './actions'; + +declare module '../../../../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [OPEN_FLYOUT_ADD_DRILLDOWN]: FlyoutCreateDrilldownActionContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: FlyoutEditDrilldownActionContext; + } +} + +export class DashboardDrilldownsService { + bootstrap(core: CoreSetup, { uiActions }: Pick) { + const overlays = async () => (await core.getStartServices())[0].overlays; + + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays }); + uiActions.registerAction(actionFlyoutCreateDrilldown); + uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); + + const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays }); + uiActions.registerAction(actionFlyoutEditDrilldown); + uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); + } +} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts new file mode 100644 index 0000000000000..7be8f1c65da12 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dashboard_drilldowns_services'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/index.ts new file mode 100644 index 0000000000000..8cc3e12906531 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/mocks.ts b/x-pack/plugins/drilldowns/public/mocks.ts index bfade1674072a..97a430fad260a 100644 --- a/x-pack/plugins/drilldowns/public/mocks.ts +++ b/x-pack/plugins/drilldowns/public/mocks.ts @@ -22,7 +22,7 @@ const createStartContract = (): Start => { return startContract; }; -export const bfetchPluginMock = { +export const drilldownsPluginMock = { createSetupContract, createStartContract, }; diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index ab7f5cc0f52f6..7a2947913d43f 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -7,13 +7,6 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DrilldownService } from './service'; -import { - FlyoutCreateDrilldownActionContext, - FlyoutEditDrilldownActionContext, - OPEN_FLYOUT_ADD_DRILLDOWN, - OPEN_FLYOUT_EDIT_DRILLDOWN, -} from './actions'; -import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; export interface DrilldownsSetupDependencies { uiActions: UiActionsSetup; @@ -30,13 +23,6 @@ export type DrilldownsSetupContract = Pick (await core.getStartServices())[0].overlays; - - const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ - overlays, - getDrilldownActionFactories: () => advancedUiActions.actionFactory.getAll(), - }); - uiActions.registerAction(actionFlyoutCreateDrilldown); - uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); - - const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ - overlays, - getDrilldownActionFactories: () => advancedUiActions.actionFactory.getAll(), - }); - uiActions.registerAction(actionFlyoutEditDrilldown); - uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); - // TODO: mocks advancedUiActions.actionFactory.register(dashboardDrilldownActionFactory); advancedUiActions.actionFactory.register(urlDrilldownActionFactory); From 1f2cc4c38f724c7426fddb4ec95cd24cadb15e53 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 9 Mar 2020 15:23:06 +0100 Subject: [PATCH 06/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20"implement"=20regi?= =?UTF-8?q?sterDrilldown()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/drilldowns/public/index.ts | 8 ++-- x-pack/plugins/drilldowns/public/plugin.ts | 27 ++++++------- .../public/service/drilldown_service.ts | 38 +++++++++++++------ 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 63e7a12235462..48eeb142b65a2 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -7,10 +7,10 @@ import { DrilldownsPlugin } from './plugin'; export { - DrilldownsSetupContract, - DrilldownsSetupDependencies, - DrilldownsStartContract, - DrilldownsStartDependencies, + SetupContract as DrilldownsSetupContract, + SetupDependencies as DrilldownsSetupDependencies, + StartContract as DrilldownsStartContract, + StartDependencies as DrilldownsStartDependencies, } from './plugin'; export function plugin() { diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index 7a2947913d43f..b4124704a1a7c 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -6,38 +6,35 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; -import { DrilldownService } from './service'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; +import { DrilldownService, DrilldownServiceSetupContract } from './service'; -export interface DrilldownsSetupDependencies { +export interface SetupDependencies { uiActions: UiActionsSetup; advancedUiActions: AdvancedUiActionsSetup; } -export interface DrilldownsStartDependencies { +export interface StartDependencies { uiActions: UiActionsStart; advancedUiActions: AdvancedUiActionsStart; } -export type DrilldownsSetupContract = Pick; +export type SetupContract = DrilldownServiceSetupContract; // eslint-disable-next-line -export interface DrilldownsStartContract {} +export interface StartContract {} export class DrilldownsPlugin - implements - Plugin< - DrilldownsSetupContract, - DrilldownsStartContract, - DrilldownsSetupDependencies, - DrilldownsStartDependencies - > { + implements Plugin { private readonly service = new DrilldownService(); - public setup(core: CoreSetup, plugins: DrilldownsSetupDependencies): DrilldownsSetupContract { - return this.service; + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + const setup = this.service.setup(core, plugins); + + return setup; } - public start(core: CoreStart, plugins: DrilldownsStartDependencies): DrilldownsStartContract { + public start(core: CoreStart, plugins: StartDependencies): StartContract { return {}; } diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts index 983db1ee9f945..6a27094b80457 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -5,7 +5,8 @@ */ import { CoreSetup } from 'src/core/public'; -import { DrilldownsSetupDependencies } from '../plugin'; +import { AdvancedUiActionsSetup, ActionFactory } from '../../../advanced_ui_actions/public'; + // TODO: MOCK DATA import { dashboardDrilldownActionFactory, @@ -13,18 +14,31 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../advanced_ui_actions/public/components/action_wizard/test_data'; -export class DrilldownService { - bootstrap(core: CoreSetup, { uiActions, advancedUiActions }: DrilldownsSetupDependencies) { - // TODO: mocks - advancedUiActions.actionFactory.register(dashboardDrilldownActionFactory); - advancedUiActions.actionFactory.register(urlDrilldownActionFactory); - } +export interface DrilldownServiceSetupDeps { + advancedUiActions: AdvancedUiActionsSetup; +} +export interface DrilldownServiceSetupContract { /** - * Convenience method to register a drilldown. (It should set-up all the - * necessary triggers and actions.) + * Convenience method to register a drilldown. */ - registerDrilldown = (): void => { - throw new Error('not implemented'); - }; + registerDrilldown: (drilldownFactory: ActionFactory) => void; +} + +export class DrilldownService { + setup( + core: CoreSetup, + { advancedUiActions }: DrilldownServiceSetupDeps + ): DrilldownServiceSetupContract { + const registerDrilldown: DrilldownServiceSetupContract['registerDrilldown'] = drilldownFactory => { + advancedUiActions.actionFactory.register(drilldownFactory); + }; + + registerDrilldown(dashboardDrilldownActionFactory); + registerDrilldown(urlDrilldownActionFactory); + + return { + registerDrilldown, + }; + } } From 86885c45eed7e5f716e459562ad9e8b0aa408474 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 9 Mar 2020 15:43:16 +0100 Subject: [PATCH 07/79] fix ConfigurableBaseConfig type --- src/plugins/ui_actions/public/index.ts | 2 +- src/plugins/ui_actions/public/util/configurable.ts | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index f52d1e2af9a88..21107608b5f11 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,7 +35,7 @@ export { ActionContract as UiActionsActionContract, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; -export { CollectConfigProps, Presentable, Configurable } from './util'; +export { CollectConfigProps, Presentable, Configurable, ConfigurableBaseConfig } from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index 4daa601e93779..d2586db52ec9f 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -19,13 +19,12 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ConfigurableBaseConfig {} +export type ConfigurableBaseConfig = object; /** * Represents something that can be configured by user using UI. */ -export interface Configurable { +export interface Configurable { /** * Create default config for this item, used when item is created for the first time. */ @@ -45,7 +44,9 @@ export interface Configurable { /** * Props provided to `CollectConfig` component on every re-render. */ -export interface CollectConfigProps { +export interface CollectConfigProps< + Config extends ConfigurableBaseConfig = ConfigurableBaseConfig +> { /** * Current (latest) config of the item. */ From 64631188864995287f057a82872e15bbbcf730dd Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 9 Mar 2020 18:10:24 +0100 Subject: [PATCH 08/79] Implement connected flyout_manage_drilldowns component --- .../dashboard_enhanced/public/plugin.ts | 5 +- .../actions/flyout_create_drilldown/index.tsx | 19 ++---- .../actions/flyout_edit_drilldown/index.tsx | 24 +++++--- .../dashboard_drilldowns_services.ts | 11 +++- .../connected_flyout_manage_drilldowns.tsx | 61 +++++++++++++++++++ .../index.ts | 7 +++ .../components/form_drilldown_wizard/i18n.ts | 2 +- x-pack/plugins/drilldowns/public/mocks.ts | 4 +- x-pack/plugins/drilldowns/public/plugin.ts | 9 ++- 9 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx create mode 100644 x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 9943817631db2..447ceff4b8cb2 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -7,13 +7,16 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DashboardDrilldownsService } from './services'; +import { DrilldownsSetupContract, DrilldownsStartContract } from '../../drilldowns/public'; export interface SetupDependencies { uiActions: UiActionsSetup; + drilldowns: DrilldownsSetupContract; } export interface StartDependencies { uiActions: UiActionsStart; + drilldowns: DrilldownsStartContract; } // eslint-disable-next-line @@ -26,7 +29,7 @@ export class DashboardEnhancedPlugin implements Plugin { public readonly drilldowns = new DashboardDrilldownsService(); - public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.drilldowns.bootstrap(core, plugins); return {}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index 7c98348b307f3..459f7f568ea78 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -10,8 +10,7 @@ import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; - -const FlyoutDrilldownWizard: React.FC<{ onClose: any }> = () =>
FormDrilldownWizard
; +import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -21,6 +20,7 @@ export interface FlyoutCreateDrilldownActionContext { export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; + drilldowns: () => Promise; } export class FlyoutCreateDrilldownAction implements ActionByType { @@ -46,21 +46,14 @@ export class FlyoutCreateDrilldownAction implements ActionByType factory.isCompatible(context)) - ).then(compatibilityList => - drilldownActionFactories.filter((factory, index) => compatibilityList[index]) - ); - */ + const drilldowns = await this.params.drilldowns(); const handle = overlays.openFlyout( toMountPoint( - handle.close()} - // drilldownActionFactories={compatibleDrilldownActionFactories} + context={context} + viewMode={'create'} /> ) ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index 7bd2030786117..47a82c86d321b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -14,13 +14,7 @@ import { toMountPoint, } from '../../../../../../../../src/plugins/kibana_react/public'; import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; - -const FlyoutManageDrilldowns: React.FC<{ onClose: () => {} }> = () => ( -
FormDrilldownWizard
-); - -// Mock data -const drilldowns: any = []; +import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -28,8 +22,11 @@ export interface FlyoutEditDrilldownActionContext { embeddable: IEmbeddable; } +const drilldownsData = [{}, {}]; + export interface FlyoutEditDrilldownParams { overlays: () => Promise; + drilldowns: () => Promise; } const displayName = i18n.translate('xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName', { @@ -56,7 +53,7 @@ export class FlyoutEditDrilldownAction implements ActionByType {displayName}{' '} - {drilldowns.length} + {drilldownsData.length} ); @@ -65,14 +62,21 @@ export class FlyoutEditDrilldownAction implements ActionByType 0; + return embeddable.getInput().viewMode === 'edit' && drilldownsData.length > 0; } public async execute(context: FlyoutEditDrilldownActionContext) { const overlays = await this.params.overlays(); + const drilldowns = await this.params.drilldowns(); const handle = overlays.openFlyout( - toMountPoint( handle.close()} />) + toMountPoint( + handle.close()} + context={context} + viewMode={'manage'} + /> + ) ); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index fb9164227156a..74be910255b17 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -15,6 +15,7 @@ import { OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; +import { DrilldownsStartContract } from '../../../../drilldowns/public'; declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -24,14 +25,18 @@ declare module '../../../../../../src/plugins/ui_actions/public' { } export class DashboardDrilldownsService { - bootstrap(core: CoreSetup, { uiActions }: Pick) { + bootstrap( + core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, + { uiActions }: Pick + ) { const overlays = async () => (await core.getStartServices())[0].overlays; + const drilldowns = async () => (await core.getStartServices())[1].drilldowns; - const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays }); + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); uiActions.registerAction(actionFlyoutCreateDrilldown); uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); - const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays }); + const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); uiActions.registerAction(actionFlyoutEditDrilldown); uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); } diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx new file mode 100644 index 0000000000000..851eac616d24e --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FlyoutManageDrilldowns } from '../flyout_manage_drilldowns'; +import { ActionFactory, AdvancedUiActionsStart } from '../../../../advanced_ui_actions/public'; +import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; + +interface ConnectedFlyoutManageDrilldownsProps { + context: Context; + viewMode?: 'create' | 'manage'; + onClose?: () => void; +} + +export function createFlyoutManageDrilldowns({ + advancedUiActions, +}: { + advancedUiActions: AdvancedUiActionsStart; +}) { + return (props: ConnectedFlyoutManageDrilldownsProps) => { + const [compatibleActionFactories, setCompatibleActionFactories] = useState< + Array> + >(); + useEffect(() => { + async function updateCompatibleFactoriesForContext() { + const allActionFactories = advancedUiActions.actionFactory.getAll(); + const compatibility = await Promise.all( + allActionFactories.map(factory => factory.isCompatible(props.context)) + ); + setCompatibleActionFactories(allActionFactories.filter((_, i) => compatibility[i])); + } + updateCompatibleFactoriesForContext(); + }, [props.context]); + + if (!compatibleActionFactories) return null; + + switch (props.viewMode) { + case 'create': + return ( + + ); + + case 'manage': + default: + return ( + + ); + } + }; +} diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts new file mode 100644 index 0000000000000..f084a3e563c23 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './connected_flyout_manage_drilldowns'; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts index 2dfcd917a7900..ba6975ebeed3c 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const txtNameOfDrilldown = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown', { - defaultMessage: 'Name of drilldown', + defaultMessage: 'Name of drilldown:', } ); diff --git a/x-pack/plugins/drilldowns/public/mocks.ts b/x-pack/plugins/drilldowns/public/mocks.ts index 97a430fad260a..bab8e83d8430e 100644 --- a/x-pack/plugins/drilldowns/public/mocks.ts +++ b/x-pack/plugins/drilldowns/public/mocks.ts @@ -17,7 +17,9 @@ const createSetupContract = (): Setup => { }; const createStartContract = (): Start => { - const startContract: Start = {}; + const startContract: Start = { + FlyoutManageDrilldowns: jest.fn(), + }; return startContract; }; diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index b4124704a1a7c..c62a539fe1688 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -8,6 +8,7 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; import { DrilldownService, DrilldownServiceSetupContract } from './service'; +import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; export interface SetupDependencies { uiActions: UiActionsSetup; @@ -22,7 +23,9 @@ export interface StartDependencies { export type SetupContract = DrilldownServiceSetupContract; // eslint-disable-next-line -export interface StartContract {} +export interface StartContract { + FlyoutManageDrilldowns: ReturnType; +} export class DrilldownsPlugin implements Plugin { @@ -35,7 +38,9 @@ export class DrilldownsPlugin } public start(core: CoreStart, plugins: StartDependencies): StartContract { - return {}; + return { + FlyoutManageDrilldowns: createFlyoutManageDrilldowns(plugins), + }; } public stop() {} From d3cd9e8043600755d5ff9566b38c329946cb8fd9 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 10 Mar 2020 13:15:40 +0100 Subject: [PATCH 09/79] Simplify connected flyout manage drilldowns component. Remove intermediate component --- .../advanced_ui_actions/public/index.ts | 4 +- .../advanced_ui_actions/public/plugin.ts | 4 +- ...nected_flyout_manage_drilldowns.story.tsx} | 18 ++- ...onnected_flyout_manage_drilldowns.test.tsx | 63 +++++++++++ .../connected_flyout_manage_drilldowns.tsx | 104 ++++++++++++++---- .../flyout_drilldown_wizard.tsx | 2 +- .../flyout_manage_drilldowns.tsx | 78 ------------- .../flyout_manage_drilldowns/i18n.ts | 14 --- .../flyout_manage_drilldowns/index.ts | 7 -- 9 files changed, 162 insertions(+), 132 deletions(-) rename x-pack/plugins/drilldowns/public/components/{flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx => connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx} (66%) create mode 100644 x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx delete mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx delete mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts delete mode 100644 x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index abecbf354734c..04fca487214c0 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -14,5 +14,5 @@ export function plugin(initializerContext: PluginInitializerContext) { export { AdvancedUiActionsPublicPlugin as Plugin }; export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; -export * from './components'; -export * from './ui_actions_factory'; +export { ActionWizard } from './components'; +export { ActionFactory, ActionBaseConfig } from './ui_actions_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 73592cb18cebf..8acf89fe5a37d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -43,10 +43,10 @@ interface StartDependencies { } export interface AdvancedUiActionsSetup { - actionFactory: Pick; + actionFactory: Pick; } export interface AdvancedUiActionsStart { - actionFactory: Pick; + actionFactory: Pick; } declare module '../../../../src/plugins/ui_actions/public' { diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx similarity index 66% rename from x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx rename to x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index 4fbe874f01561..a2b1975a4c428 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -7,19 +7,25 @@ import * as React from 'react'; import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; -import { FlyoutManageDrilldowns } from './flyout_manage_drilldowns'; -import { drilldowns } from '../list_manage_drilldowns/test_data'; +import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; import { dashboardDrilldownActionFactory, urlDrilldownActionFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ + advancedUiActions: { + actionFactory: { + getAll: () => { + return [dashboardDrilldownActionFactory, urlDrilldownActionFactory]; + }, + }, + }, +}); + storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx new file mode 100644 index 0000000000000..299f7672ac714 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { cleanup, fireEvent, render, wait } from '@testing-library/react/pure'; +import '@testing-library/jest-dom/extend-expect'; +import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; +import { + dashboardDrilldownActionFactory, + urlDrilldownActionFactory, +} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; + +const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ + advancedUiActions: { + actionFactory: { + getAll: () => { + return [dashboardDrilldownActionFactory, urlDrilldownActionFactory]; + }, + }, + }, +}); + +// https://github.com/elastic/kibana/issues/59469 +afterEach(cleanup); + +test(' should render in manage view and should allow to create new drilldown', async () => { + const screen = render(); + + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + + fireEvent.click(screen.getByText(/Create new/i)); + + let [createHeading, createButton] = screen.getAllByText(/Create Drilldown/i); + expect(createHeading).toBeVisible(); + expect(screen.getByLabelText(/Back/i)).toBeVisible(); + + expect(createButton).toBeDisabled(); + + // input drilldown name + fireEvent.change(screen.getByLabelText(/name/i), { + target: { value: 'Test' }, + }); + + // select URL one + fireEvent.click(screen.getByText(/Go to URL/i)); + + // Input url + const URL = 'https://elastic.co'; + fireEvent.change(screen.getByLabelText(/url/i), { + target: { value: URL }, + }); + + [createHeading, createButton] = screen.getAllByText(/Create Drilldown/i); + + expect(createButton).toBeEnabled(); + fireEvent.click(createButton); + + expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); +}); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 851eac616d24e..a49dadb527d5e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -5,9 +5,9 @@ */ import React, { useEffect, useState } from 'react'; -import { FlyoutManageDrilldowns } from '../flyout_manage_drilldowns'; import { ActionFactory, AdvancedUiActionsStart } from '../../../../advanced_ui_actions/public'; import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; +import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; interface ConnectedFlyoutManageDrilldownsProps { context: Context; @@ -15,47 +15,107 @@ interface ConnectedFlyoutManageDrilldownsProps onClose?: () => void; } +/** + * Represent current state (route) of FlyoutManageDrilldowns + */ +enum Routes { + Manage = 'manage', + Create = 'create', + Edit = 'edit', +} + export function createFlyoutManageDrilldowns({ advancedUiActions, }: { advancedUiActions: AdvancedUiActionsStart; }) { + // This is ok to assume this is static, + // because all action factories should be registerd in setup phase + const allActionFactories = advancedUiActions.actionFactory.getAll(); return (props: ConnectedFlyoutManageDrilldownsProps) => { - const [compatibleActionFactories, setCompatibleActionFactories] = useState< - Array> - >(); - useEffect(() => { - async function updateCompatibleFactoriesForContext() { - const allActionFactories = advancedUiActions.actionFactory.getAll(); - const compatibility = await Promise.all( - allActionFactories.map(factory => factory.isCompatible(props.context)) - ); - setCompatibleActionFactories(allActionFactories.filter((_, i) => compatibility[i])); - } - updateCompatibleFactoriesForContext(); - }, [props.context]); + const isCreateOnly = props.viewMode === 'create'; + + const actionFactories = useCompatibleActionFactoriesForCurrentContext( + allActionFactories, + props.context + ); - if (!compatibleActionFactories) return null; + const [route, setRoute] = useState( + () => (isCreateOnly ? Routes.Create : Routes.Manage) // initial state is different depending on `viewMode` + ); - switch (props.viewMode) { - case 'create': + /** + * isCompatible promise is not yet resolved. + * Skip rendering until it is resolved + */ + if (!actionFactories) return null; + + switch (route) { + case Routes.Create: + case Routes.Edit: return ( setRoute(Routes.Manage)} + onSubmit={() => { + if (isCreateOnly) { + if (props.onClose) { + props.onClose(); + } + } else { + setRoute(Routes.Manage); + } + }} + onDelete={() => { + setRoute(Routes.Manage); + }} /> ); - case 'manage': + case Routes.Manage: default: return ( - {}} + onEdit={() => { + setRoute(Routes.Edit); + }} + onCreate={() => { + setRoute(Routes.Create); + }} onClose={props.onClose} /> ); } }; } + +function useCompatibleActionFactoriesForCurrentContext( + actionFactories: Array>, + context: Context +) { + const [compatibleActionFactories, setCompatibleActionFactories] = useState< + Array> + >(); + useEffect(() => { + let canceled = false; + async function updateCompatibleFactoriesForContext() { + const compatibility = await Promise.all( + actionFactories.map(factory => factory.isCompatible(context)) + ); + if (canceled) return; + setCompatibleActionFactories(actionFactories.filter((_, i) => compatibility[i])); + } + updateCompatibleFactoriesForContext(); + return () => { + canceled = true; + }; + }, [context, actionFactories]); + + return compatibleActionFactories; +} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index d8d7e253bb81a..8c53e9a7fb8cc 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -50,7 +50,7 @@ export function FlyoutDrilldownWizard< initialDrilldownWizardConfig, mode = 'create', onDelete = () => {}, - showWelcomeMessage = false, + showWelcomeMessage = true, onWelcomeHideClick, drilldownActionFactories, }: FlyoutDrilldownWizardProps) { diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx deleted file mode 100644 index 152b49f62fddd..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/flyout_manage_drilldowns.tsx +++ /dev/null @@ -1,78 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useState } from 'react'; -import { DrilldownListItem } from '../list_manage_drilldowns'; -import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; -import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; -import { ActionFactory } from '../../../../advanced_ui_actions/public'; - -export interface FlyoutManageDrilldownsProps { - drilldowns: DrilldownListItem[]; - drilldownActionFactories: Array>; - - onClose?: () => void; - showWelcomeMessage?: boolean; - onHideWelcomeMessage?: () => void; -} - -enum ViewState { - List = 'list', - Create = 'create', - Edit = 'edit', -} - -export function FlyoutManageDrilldowns({ - drilldowns, - onClose = () => {}, - showWelcomeMessage = true, - onHideWelcomeMessage, - drilldownActionFactories, -}: FlyoutManageDrilldownsProps) { - const [viewState, setViewState] = useState(ViewState.List); - - // TODO: apparently this will be the component with all the state management and data fetching - - switch (viewState) { - case ViewState.Create: - case ViewState.Edit: - return ( - setViewState(ViewState.List)} - onDelete={() => { - setViewState(ViewState.List); - }} - onClose={() => { - onClose(); - }} - onBack={() => { - setViewState(ViewState.List); - }} - showWelcomeMessage={showWelcomeMessage} - onWelcomeHideClick={onHideWelcomeMessage} - /> - ); - case ViewState.List: - default: - return ( - { - setViewState(ViewState.Create); - }} - onEdit={() => { - setViewState(ViewState.Edit); - }} - onDelete={() => {}} - /> - ); - } -} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts deleted file mode 100644 index 460fcf2e06c0e..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/i18n.ts +++ /dev/null @@ -1,14 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtManageDrilldowns = i18n.translate( - 'xpack.drilldowns.components.FlyoutManageDrilldowns.manageDrilldownsTitle', - { - defaultMessage: 'Manage Drilldowns', - } -); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts b/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts deleted file mode 100644 index c1c530977a122..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/flyout_manage_drilldowns/index.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './flyout_manage_drilldowns'; From 840e873a44b9a613fd2688f4ffa1280147d21068 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 10 Mar 2020 13:29:21 +0100 Subject: [PATCH 10/79] clean up data-testid workaround in new components --- .../public/components/action_wizard/action_wizard.tsx | 2 -- .../list_manage_drilldowns/list_manage_drilldowns.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 930ec7e15cb01..7bd386a52509b 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -106,7 +106,6 @@ const SelectedActionFactory: React.FC = ({
@@ -164,7 +163,6 @@ const ActionFactorySelector: React.FC = ({ className="auaActionWizard__actionFactoryItem" key={actionFactory.id} label={actionFactory.getDisplayName()} - data-testid={TEST_SUBJ_ACTION_FACTORY_ITEM} data-test-subj={TEST_SUBJ_ACTION_FACTORY_ITEM} onClick={() => onActionFactorySelected(actionFactory)} > diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index a9895444558cd..cc61ac924ebed 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -81,7 +81,6 @@ export function ListManageDrilldowns({ }} rowProps={{ 'data-test-subj': TEST_SUBJ_DRILLDOWN_ITEM, - 'data-testid': TEST_SUBJ_DRILLDOWN_ITEM, }} hasActions={true} /> From f0579799779d1c7898519a1c1f723d87a2034638 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 10 Mar 2020 14:41:28 +0100 Subject: [PATCH 11/79] Connect welcome message to storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure, but use LocalStorage. Didn’t find a way to persist user settings. looks like uiSettings are not user scoped. --- ...nnected_flyout_manage_drilldowns.story.tsx | 3 +++ ...onnected_flyout_manage_drilldowns.test.tsx | 25 ++++++++++++++++++ .../connected_flyout_manage_drilldowns.tsx | 26 +++++++++++++++++-- .../drilldown_hello_bar.tsx | 1 + .../form_drilldown_wizard.tsx | 1 - x-pack/plugins/drilldowns/public/plugin.ts | 6 ++++- 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index a2b1975a4c428..7031db9987b1b 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -13,6 +13,8 @@ import { urlDrilldownActionFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { @@ -22,6 +24,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ }, }, }, + storage: new Storage(new StubBrowserStorage()), }); storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index 299f7672ac714..540eeb056321e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -12,7 +12,10 @@ import { dashboardDrilldownActionFactory, urlDrilldownActionFactory, } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; +import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +const storage = new Storage(new StubBrowserStorage()); const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { actionFactory: { @@ -21,11 +24,16 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ }, }, }, + storage, }); // https://github.com/elastic/kibana/issues/59469 afterEach(cleanup); +beforeEach(() => { + storage.clear(); +}); + test(' should render in manage view and should allow to create new drilldown', async () => { const screen = render(); @@ -61,3 +69,20 @@ test(' should render in manage view and should allow to expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); }); + +test('Should show drilldown welcome message. Should be able to dismiss it', async () => { + let screen = render(); + + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + const welcomeMessageTestSubj = 'drilldowns-welcome-message-test-subj'; + expect(screen.getByTestId(welcomeMessageTestSubj)).toBeVisible(); + fireEvent.click(screen.getByText(/hide/i)); + expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); + cleanup(); + + screen = render(); + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); +}); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index a49dadb527d5e..de80928b68668 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react'; import { ActionFactory, AdvancedUiActionsStart } from '../../../../advanced_ui_actions/public'; import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; +import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; interface ConnectedFlyoutManageDrilldownsProps { context: Context; @@ -26,12 +27,15 @@ enum Routes { export function createFlyoutManageDrilldowns({ advancedUiActions, + storage, }: { advancedUiActions: AdvancedUiActionsStart; + storage: IStorageWrapper; }) { // This is ok to assume this is static, // because all action factories should be registerd in setup phase const allActionFactories = advancedUiActions.actionFactory.getAll(); + return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; @@ -44,6 +48,8 @@ export function createFlyoutManageDrilldowns({ () => (isCreateOnly ? Routes.Create : Routes.Manage) // initial state is different depending on `viewMode` ); + const [shouldShowWelcomeMessage, onHideWelcomeMessage] = useWelcomeMessage(storage); + /** * isCompatible promise is not yet resolved. * Skip rendering until it is resolved @@ -55,7 +61,8 @@ export function createFlyoutManageDrilldowns({ case Routes.Edit: return ( {}} onEdit={() => { @@ -119,3 +127,17 @@ function useCompatibleActionFactoriesForCurrentContext void] { + const key = `drilldowns:hidWelcomeMessage`; + const [hidWelcomeMessage, setHidWelcomeMessage] = useState(storage.get(key) ?? false); + + return [ + !hidWelcomeMessage, + () => { + if (hidWelcomeMessage) return; + setHidWelcomeMessage(true); + storage.set(key, true); + }, + ]; +} diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index bec9d0145c2b3..60ac90a954c27 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -29,6 +29,7 @@ export const DrilldownHelloBar: React.FC = ({ return ( diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index 19ec5b7506c1b..6913097c7c456 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -69,7 +69,6 @@ export const FormDrilldownWizard: React.FC = ({ return ( <> - {nameFragment} diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index c62a539fe1688..d2fa88bd21745 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -9,6 +9,7 @@ import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actio import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; import { DrilldownService, DrilldownServiceSetupContract } from './service'; import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; export interface SetupDependencies { uiActions: UiActionsSetup; @@ -39,7 +40,10 @@ export class DrilldownsPlugin public start(core: CoreStart, plugins: StartDependencies): StartContract { return { - FlyoutManageDrilldowns: createFlyoutManageDrilldowns(plugins), + FlyoutManageDrilldowns: createFlyoutManageDrilldowns({ + advancedUiActions: plugins.advancedUiActions, + storage: new Storage(localStorage), + }), }; } From d60fc96a72daa51f32365c7f43d51088556bae5f Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 10 Mar 2020 17:04:35 +0100 Subject: [PATCH 12/79] require `context` in Presentable. drill context down through wizard components --- src/plugins/ui_actions/public/index.ts | 8 +++- .../ui_actions/public/util/presentable.ts | 10 +++-- .../action_wizard/action_wizard.tsx | 43 +++++++++++++------ .../components/action_wizard/test_data.tsx | 1 + .../advanced_ui_actions/public/index.ts | 7 ++- .../ui_actions_factory_service.ts | 17 +++++--- .../connected_flyout_manage_drilldowns.tsx | 2 + .../drilldown_hello_bar.scss | 2 - .../flyout_drilldown_wizard.tsx | 13 +++++- .../flyout_list_manage_drilldowns.tsx | 2 + .../form_drilldown_wizard.tsx | 17 +++++--- .../list_manage_drilldowns.tsx | 3 ++ 12 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 21107608b5f11..08d20acd63ce4 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,7 +35,13 @@ export { ActionContract as UiActionsActionContract, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; -export { CollectConfigProps, Presentable, Configurable, ConfigurableBaseConfig } from './util'; +export { + CollectConfigProps, + Presentable, + Configurable, + ConfigurableBaseConfig, + PresentableBaseContext, +} from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index 464802eccd0b6..daaf316cf402d 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -19,10 +19,12 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; +export type PresentableBaseContext = object; + /** * Represents something that can be displayed to user in UI. */ -export interface Presentable { +export interface Presentable { /** * ID that uniquely identifies this object. */ @@ -43,16 +45,16 @@ export interface Presentable { /** * Optional EUI icon type that can be displayed along with the title. */ - getIconType(context?: Context): string | undefined; + getIconType(context: Context): string | undefined; /** * Returns a title to be displayed to the user. */ - getDisplayName(context?: Context): string; + getDisplayName(context: Context): string; /** * Returns a promise that resolves to true if this item is compatible given * the context and should be displayed to user, otherwise resolves to false. */ - isCompatible(context?: Context): Promise; + isCompatible(context: Context): Promise; } diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 7bd386a52509b..b88cee84e0e21 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -16,14 +16,19 @@ import { } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; -import { ActionBaseConfig, ActionFactory } from '../../ui_actions_factory'; +import { + ActionBaseConfig, + ActionFactory, + ActionFactoryList, + ActionFactoryBaseContext, +} from '../../ui_actions_factory'; import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; export interface ActionWizardProps { /** * List of available action factories */ - actionFactories: Array>; // any here to be able to pass array of ActionFactory with different configs + actionFactories: ActionFactoryList; /** * Currently selected action factory @@ -45,6 +50,11 @@ export interface ActionWizardProps { * config changed */ onConfigChange: (config: ActionBaseConfig) => void; + + /** + * Context will be passed into ActionFactory's methods + */ + context: ActionFactoryBaseContext; } export const ActionWizard: React.FC = ({ @@ -53,6 +63,7 @@ export const ActionWizard: React.FC = ({ onActionFactoryChange, onConfigChange, config, + context, }) => { // auto pick action factory if there is only 1 available if (!currentActionFactory && actionFactories.length === 1) { @@ -67,6 +78,7 @@ export const ActionWizard: React.FC = ({ onDeselect={() => { onActionFactoryChange(null); }} + context={context} config={config} onConfigChange={newConfig => { onConfigChange(newConfig); @@ -77,6 +89,7 @@ export const ActionWizard: React.FC = ({ return ( { onActionFactoryChange(actionFactory); @@ -85,10 +98,11 @@ export const ActionWizard: React.FC = ({ ); }; -interface SelectedActionFactoryProps { - actionFactory: ActionFactory; - config: Config; - onConfigChange: (config: Config) => void; +interface SelectedActionFactoryProps { + actionFactory: ActionFactory; + config: ActionBaseConfig; + context: ActionFactoryBaseContext; + onConfigChange: (config: ActionBaseConfig) => void; showDeselect: boolean; onDeselect: () => void; } @@ -101,6 +115,7 @@ const SelectedActionFactory: React.FC = ({ showDeselect, onConfigChange, config, + context, }) => { return (
= ({ >
- {actionFactory.getIconType() && ( + {actionFactory.getIconType(context) && ( - + )} -

{actionFactory.getDisplayName()}

+

{actionFactory.getDisplayName(context)}

{showDeselect && ( @@ -140,7 +155,8 @@ const SelectedActionFactory: React.FC = ({ }; interface ActionFactorySelectorProps { - actionFactories: ActionFactory[]; + actionFactories: ActionFactoryList; + context: ActionFactoryBaseContext; onActionFactorySelected: (actionFactory: ActionFactory) => void; } @@ -149,6 +165,7 @@ export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; const ActionFactorySelector: React.FC = ({ actionFactories, onActionFactorySelected, + context, }) => { if (actionFactories.length === 0) { // this is not user facing, as it would be impossible to get into this state @@ -162,11 +179,13 @@ const ActionFactorySelector: React.FC = ({ onActionFactorySelected(actionFactory)} > - {actionFactory.getIconType() && } + {actionFactory.getIconType(context) && ( + + )} ))}
diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index d08bd299dddd8..e88399bdc07be 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -179,6 +179,7 @@ export function Demo({ actionFactories }: { actionFactories: Array

diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 04fca487214c0..74dfd3c6012b6 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -15,4 +15,9 @@ export { AdvancedUiActionsPublicPlugin as Plugin }; export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; export { ActionWizard } from './components'; -export { ActionFactory, ActionBaseConfig } from './ui_actions_factory'; +export { + ActionFactory, + ActionBaseConfig, + ActionFactoryBaseContext, + ActionFactoryList, +} from './ui_actions_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts index 5c9879f11b2f5..45f5382c9ec1d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts +++ b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts @@ -6,16 +6,21 @@ import { Presentable, + PresentableBaseContext, Configurable, ConfigurableBaseConfig, } from '../../../../../src/plugins/ui_actions/public'; export type ActionBaseConfig = ConfigurableBaseConfig; -export interface ActionFactory - extends Presentable, - Configurable {} +export type ActionFactoryBaseContext = PresentableBaseContext; +export interface ActionFactory< + ActionConfig extends ActionBaseConfig = ActionBaseConfig, + ActionFactoryContext extends ActionFactoryBaseContext = ActionFactoryBaseContext +> extends Presentable, Configurable {} -type ActionFactoryRegistry = Map>; +export type ActionFactoryList = Array>; + +type ActionFactoryRegistry = Map>; export class UiActionsFactoryService { protected readonly actionFactories: ActionFactoryRegistry; @@ -24,7 +29,7 @@ export class UiActionsFactoryService { this.actionFactories = actionFactories; } - public readonly register = (actionFactory: ActionFactory) => { + public readonly register = (actionFactory: ActionFactory) => { if (this.actionFactories.has(actionFactory.id)) { throw new Error(`ActionFactory [actionFactory.id = ${actionFactory.id}] already registered.`); } @@ -32,7 +37,7 @@ export class UiActionsFactoryService { this.actionFactories.set(actionFactory.id, actionFactory); }; - public readonly getAll = (): Array> => { + public readonly getAll = (): Array> => { return Array.from(this.actionFactories.values()); }; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index de80928b68668..ef3372deb6429 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -79,6 +79,7 @@ export function createFlyoutManageDrilldowns({ onDelete={() => { setRoute(Routes.Manage); }} + actionFactoryContext={props.context} /> ); @@ -97,6 +98,7 @@ export function createFlyoutManageDrilldowns({ setRoute(Routes.Create); }} onClose={props.onClose} + context={props.context} /> ); } diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss index 241f56731ab7b..e527485765df3 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss @@ -1,5 +1,3 @@ -@import '../../../../../../src/legacy/ui/public/styles/_styling_constants'; - .drdHelloBar__content .euiFlexItem { margin: $euiSizeL; // increase spacing between elements } diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index 8c53e9a7fb8cc..8f6b9d4bc97ff 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -15,7 +15,12 @@ import { txtEditDrilldownButtonLabel, txtEditDrilldownTitle, } from './i18n'; -import { ActionBaseConfig, ActionFactory } from '../../../../advanced_ui_actions/public'; +import { + ActionBaseConfig, + ActionFactory, + ActionFactoryBaseContext, + ActionFactoryList, +} from '../../../../advanced_ui_actions/public'; import { DrilldownHelloBar } from '../drilldown_hello_bar'; export interface DrilldownWizardConfig { @@ -27,7 +32,7 @@ export interface DrilldownWizardConfig { - drilldownActionFactories: Array>; + drilldownActionFactories: ActionFactoryList; onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void; onDelete?: () => void; @@ -39,6 +44,8 @@ export interface FlyoutDrilldownWizardProps< showWelcomeMessage?: boolean; onWelcomeHideClick?: () => void; + + actionFactoryContext?: ActionFactoryBaseContext; } export function FlyoutDrilldownWizard< @@ -53,6 +60,7 @@ export function FlyoutDrilldownWizard< showWelcomeMessage = true, onWelcomeHideClick, drilldownActionFactories, + actionFactoryContext, }: FlyoutDrilldownWizardProps) { const [wizardConfig, setWizardConfig] = useState( () => @@ -123,6 +131,7 @@ export function FlyoutDrilldownWizard< } }} actionFactories={drilldownActionFactories} + actionFactoryContext={actionFactoryContext} /> {mode === 'edit' && ( <> diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx index a44a7ccccb4dc..0709608a642f3 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx @@ -18,6 +18,8 @@ export interface FlyoutListManageDrilldownsProps { onDelete?: (drilldownIds: string[]) => void; showWelcomeMessage?: boolean; onWelcomeHideClick?: () => void; + + context?: object; // TODO DrilldownBaseContext? ActionBaseContext? } export function FlyoutListManageDrilldowns({ diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index 6913097c7c456..b6675398a3218 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -12,9 +12,11 @@ import { ActionBaseConfig, ActionFactory, ActionWizard, + ActionFactoryList, + ActionFactoryBaseContext, } from '../../../../advanced_ui_actions/public'; -const noop = () => {}; +const noopFn = () => {}; export interface FormDrilldownWizardProps { name?: string; @@ -22,21 +24,23 @@ export interface FormDrilldownWizardProps { currentActionFactory?: ActionFactory; onActionFactoryChange?: (actionFactory: ActionFactory | null) => void; + actionFactoryContext?: ActionFactoryBaseContext; actionConfig?: ActionBaseConfig; onActionConfigChange?: (config: ActionBaseConfig) => void; - actionFactories?: Array>; + actionFactories?: ActionFactoryList; } export const FormDrilldownWizard: React.FC = ({ name = '', actionConfig, currentActionFactory, - onNameChange = noop, - onActionConfigChange = noop, - onActionFactoryChange = noop, + onNameChange = noopFn, + onActionConfigChange = noopFn, + onActionFactoryChange = noopFn, actionFactories = [], + actionFactoryContext = {}, }) => { const nameFragment = ( @@ -44,7 +48,7 @@ export const FormDrilldownWizard: React.FC = ({ name="drilldown_name" placeholder={txtUntitledDrilldown} value={name} - disabled={onNameChange === noop} + disabled={onNameChange === noopFn} onChange={event => onNameChange(event.target.value)} data-test-subj="dynamicActionNameInput" /> @@ -63,6 +67,7 @@ export const FormDrilldownWizard: React.FC = ({ config={actionConfig} onActionFactoryChange={actionFactory => onActionFactoryChange(actionFactory)} onConfigChange={config => onActionConfigChange(config)} + context={actionFactoryContext} /> ); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index cc61ac924ebed..e3383596ea330 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -32,6 +32,8 @@ export interface ListManageDrilldownsProps { onEdit?: (id: string) => void; onCreate?: () => void; onDelete?: (ids: string[]) => void; + + context?: object; // TODO DrilldownBaseContext? ActionBaseContext? } const noop = () => {}; @@ -43,6 +45,7 @@ export function ListManageDrilldowns({ onEdit = noop, onCreate = noop, onDelete = noop, + context = {}, }: ListManageDrilldownsProps) { const [selectedDrilldowns, setSelectedDrilldowns] = useState([]); From 140c5dc1bc817801cfe8de37059156c9167858f9 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 10 Mar 2020 20:53:15 +0100 Subject: [PATCH 13/79] Drilldown factory (#59823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 import storage interface from ui_actions plugin * refactor: 💡 make actions not-dynamic * feat: 🎸 fix TypeScript errors, reshuffle types and code * fix: 🐛 fix more TypeScript errors * fix: 🐛 fix TypeScript import error --- .../embeddable_action_storage.test.ts | 55 +++++++------- .../embeddables/embeddable_action_storage.ts | 39 +++------- .../ui_actions/public/actions/action.ts | 33 ++------- .../public/actions/action_contract.ts | 38 ---------- .../public/actions/action_internal.test.ts | 71 ------------------- .../public/actions/action_internal.ts | 21 +----- .../public/actions/action_state_container.ts | 51 ------------- .../ui_actions/public/actions/index.ts | 2 +- .../configure_action.story.tsx | 55 -------------- .../configure_action/configure_action.tsx | 48 ------------- .../components/configure_action/i18n.ts | 24 ------- .../components/configure_action/index.tsx | 20 ------ .../error_configure_action.story.tsx | 7 +- src/plugins/ui_actions/public/index.ts | 12 ++-- .../public/service/ui_actions_service.ts | 11 ++- .../tests/test_samples/go_to_url_action.tsx | 67 ----------------- .../public/tests/test_samples/index.ts | 1 - src/plugins/ui_actions/public/util/index.ts | 1 - .../ui_actions/public/util/presentable.ts | 5 ++ .../action_wizard/action_wizard.story.tsx | 24 +++---- .../action_wizard/action_wizard.test.tsx | 13 +--- .../action_wizard/action_wizard.tsx | 23 +++--- .../components/action_wizard/test_data.tsx | 28 +++++--- .../advanced_ui_actions/public/index.ts | 13 ++-- .../advanced_ui_actions/public/plugin.ts | 14 ++-- .../action_factory_service/action_factory.ts | 59 +++++++++++++++ .../action_factory_definition.ts | 47 ++++++++++++ .../action_factory_service.ts | 46 ++++++++++++ .../services/action_factory_service/index.ts | 9 +++ .../{ui_actions_factory => services}/index.ts | 2 +- .../ui_actions_factory_service.ts | 47 ------------ .../public/util/configurable.ts | 19 +---- .../advanced_ui_actions/public/util/index.ts | 7 ++ ...nnected_flyout_manage_drilldowns.story.tsx | 6 +- ...onnected_flyout_manage_drilldowns.test.tsx | 6 +- .../connected_flyout_manage_drilldowns.tsx | 5 +- .../flyout_drilldown_wizard.story.tsx | 16 ++--- .../flyout_drilldown_wizard.tsx | 21 +++--- .../form_drilldown_wizard.story.tsx | 7 +- .../form_drilldown_wizard.test.tsx | 10 +-- .../form_drilldown_wizard.tsx | 19 +++-- .../public/service/drilldown_service.ts | 7 +- 42 files changed, 345 insertions(+), 664 deletions(-) delete mode 100644 src/plugins/ui_actions/public/actions/action_contract.ts delete mode 100644 src/plugins/ui_actions/public/actions/action_state_container.ts delete mode 100644 src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx delete mode 100644 src/plugins/ui_actions/public/components/configure_action/configure_action.tsx delete mode 100644 src/plugins/ui_actions/public/components/configure_action/i18n.ts delete mode 100644 src/plugins/ui_actions/public/components/configure_action/index.tsx delete mode 100644 src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts rename x-pack/plugins/advanced_ui_actions/public/{ui_actions_factory => services}/index.ts (84%) delete mode 100644 x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts rename {src/plugins/ui_actions => x-pack/plugins/advanced_ui_actions}/public/util/configurable.ts (57%) create mode 100644 x-pack/plugins/advanced_ui_actions/public/util/index.ts diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts index 56facc37fc666..f67a41596868f 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -20,7 +20,8 @@ import { Embeddable } from './embeddable'; import { EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; -import { EmbeddableActionStorage, SerializedEvent } from './embeddable_action_storage'; +import { EmbeddableActionStorage } from './embeddable_action_storage'; +import { UiActionsSerializedEvent } from '../../../../ui_actions/public'; import { of } from '../../../../kibana_utils/common'; class TestEmbeddable extends Embeddable { @@ -42,7 +43,7 @@ describe('EmbeddableActionStorage', () => { test('can add event to embeddable', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -61,17 +62,17 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID', action: {} as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID', action: {} as any, }; - const event3: SerializedEvent = { + const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', triggerId: 'TRIGGER-ID', action: {} as any, @@ -95,7 +96,7 @@ describe('EmbeddableActionStorage', () => { test('throws when creating an event with the same ID', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -122,14 +123,14 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: { @@ -148,28 +149,28 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID', action: { name: 'bar', } as any, }; - const event22: SerializedEvent = { + const event22: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID', action: { name: 'baz', } as any, }; - const event3: SerializedEvent = { + const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', triggerId: 'TRIGGER-ID', action: { @@ -199,7 +200,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -217,12 +218,12 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID', action: {} as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID', action: {} as any, @@ -249,7 +250,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -266,21 +267,21 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID', action: { name: 'foo', } as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID', action: { name: 'bar', } as any, }; - const event3: SerializedEvent = { + const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', triggerId: 'TRIGGER-ID', action: { @@ -327,7 +328,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -355,7 +356,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -383,7 +384,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: SerializedEvent = { + const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', triggerId: 'TRIGGER-ID', action: {} as any, @@ -402,17 +403,17 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID1', action: {} as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID2', action: {} as any, }; - const event3: SerializedEvent = { + const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', triggerId: 'TRIGGER-ID3', action: {} as any, @@ -502,13 +503,13 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: SerializedEvent = { + const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', triggerId: 'TRIGGER-ID1', action: {} as any, }; - const event2: SerializedEvent = { + const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', triggerId: 'TRIGGER-ID1', action: {} as any, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts index 520f92840c5f9..b9a642fafeace 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -17,32 +17,15 @@ * under the License. */ +import { UiActionsActionStorage, UiActionsSerializedEvent } from '../../../../ui_actions/public'; import { Embeddable } from '..'; -/** - * Below two interfaces are here temporarily, they will move to `ui_actions` - * plugin once #58216 is merged. - */ -export interface SerializedEvent { - eventId: string; - triggerId: string; - action: unknown; -} -export interface ActionStorage { - create(event: SerializedEvent): Promise; - update(event: SerializedEvent): Promise; - remove(eventId: string): Promise; - read(eventId: string): Promise; - count(): Promise; - list(): Promise; -} - -export class EmbeddableActionStorage implements ActionStorage { +export class EmbeddableActionStorage implements UiActionsActionStorage { constructor(private readonly embbeddable: Embeddable) {} - async create(event: SerializedEvent) { + async create(event: UiActionsSerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = (input.events || []) as UiActionsSerializedEvent[]; const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -58,9 +41,9 @@ export class EmbeddableActionStorage implements ActionStorage { }); } - async update(event: SerializedEvent) { + async update(event: UiActionsSerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = (input.events || []) as UiActionsSerializedEvent[]; const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -79,7 +62,7 @@ export class EmbeddableActionStorage implements ActionStorage { async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = (input.events || []) as UiActionsSerializedEvent[]; const index = events.findIndex(event => eventId === event.eventId); if (index === -1) { @@ -96,9 +79,9 @@ export class EmbeddableActionStorage implements ActionStorage { }); } - async read(eventId: string): Promise { + async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = (input.events || []) as UiActionsSerializedEvent[]; const event = events.find(ev => eventId === ev.eventId); if (!event) { @@ -113,14 +96,14 @@ export class EmbeddableActionStorage implements ActionStorage { private __list() { const input = this.embbeddable.getInput(); - return (input.events || []) as SerializedEvent[]; + return (input.events || []) as UiActionsSerializedEvent[]; } async count(): Promise { return this.__list().length; } - async list(): Promise { + async list(): Promise { return this.__list(); } } diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 119a152dba728..a7bbd9f116f39 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -64,11 +64,6 @@ export interface Action */ isCompatible(context: Context): Promise; - /** - * If this returns something truthy, this is used in addition to the `execute` method when clicked. - */ - getHref?(context: Context): string | undefined; - /** * Executes the action. */ @@ -78,10 +73,8 @@ export interface Action /** * A convenience interface used to register an action. */ -export interface ActionDefinition< - Context extends object = object, - Config extends object | undefined = undefined -> extends Partial> { +export interface ActionDefinition + extends Partial> { /** * ID of the action that uniquely identifies this action in the actions registry. */ @@ -92,29 +85,11 @@ export interface ActionDefinition< */ readonly type?: ActionType; - getHref?(context: Context): string | undefined; - /** * Executes the action. */ execute(context: Context): Promise; } -export type AnyActionDefinition = ActionDefinition; -export type ActionContext = A extends ActionDefinition ? Context : never; -export type ActionConfig = A extends ActionDefinition ? Config : never; - -/** - * A convenience interface used to register a dynamic action. - * - * A dynamic action is one that can be create by user and registered into the - * actions registry at runtime. User can also provide custom config for this - * action. And dynamic actions can be serialized for storage and deserialized - * back. - */ -export type DynamicActionDefinition< - Context extends object = object, - Config extends object | undefined = undefined -> = ActionDefinition; - -export type AnyDynamicActionDefinition = DynamicActionDefinition; +export type AnyActionDefinition = ActionDefinition; +export type ActionContext = A extends ActionDefinition ? Context : never; diff --git a/src/plugins/ui_actions/public/actions/action_contract.ts b/src/plugins/ui_actions/public/actions/action_contract.ts deleted file mode 100644 index 9adba9313a36d..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_contract.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ActionInternal } from './action_internal'; -import { AnyActionDefinition } from './action'; - -/** - * Action representation that is exposed out to other plugins. - */ -export type ActionContract = Pick< - ActionInternal, - | 'id' - | 'type' - | 'order' - | 'getIconType' - | 'getDisplayName' - | 'isCompatible' - | 'getHref' - | 'execute' ->; - -export type AnyActionContract = ActionContract; diff --git a/src/plugins/ui_actions/public/actions/action_internal.test.ts b/src/plugins/ui_actions/public/actions/action_internal.test.ts index 6f1528fea5f42..b14346180c274 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.test.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.test.ts @@ -19,7 +19,6 @@ import { ActionDefinition } from './action'; import { ActionInternal } from './action_internal'; -import { ActionType } from '../types'; const defaultActionDef: ActionDefinition = { id: 'test-action', @@ -31,74 +30,4 @@ describe('ActionInternal', () => { const action = new ActionInternal(defaultActionDef); expect(action.id).toBe('test-action'); }); - - describe('serialize()', () => { - test('can serialize very simple action', () => { - const action = new ActionInternal(defaultActionDef); - const serialized = action.serialize(); - - expect(serialized).toMatchObject({ - id: 'test-action', - state: expect.any(Object), - }); - }); - - test('can serialize action with modified state', () => { - const action = new ActionInternal({ - ...defaultActionDef, - type: 'ACTION_TYPE' as ActionType, - order: 11, - }); - action.state.transitions.setConfig({ foo: 'bar' }); - action.state.transitions.setName('qux'); - - const serialized = action.serialize(); - - expect(serialized).toMatchObject({ - id: 'test-action', - type: 'ACTION_TYPE', - state: { - name: 'qux', - config: { - foo: 'bar', - }, - }, - }); - }); - }); - - describe('deserialize', () => { - const serialized = { - id: 'id', - type: 'type', - state: { - name: 'name', - order: 0, - config: { - foo: 'foo', - }, - }, - }; - - test('can deserialize action state', () => { - const action = new ActionInternal({ - ...defaultActionDef, - }); - - action.deserialize(serialized); - - expect(action.state.get()).toMatchObject(serialized.state); - }); - - test('does not overwrite action id and type', () => { - const action = new ActionInternal({ - ...defaultActionDef, - }); - - action.deserialize(serialized); - - expect(action.id).toBe('test-action'); - expect(action.type).toBe(''); - }); - }); }); diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index 0edd5e3eaa602..d545757108eaa 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -19,9 +19,7 @@ import { Action, ActionContext, AnyActionDefinition } from './action'; import { Presentable } from '../util/presentable'; -// import { ActionState } from './action_state_container'; import { uiToReactComponent } from '../../../kibana_react/public'; -import { ActionContract } from './action_contract'; import { ActionType } from '../types'; export class ActionInternal @@ -34,10 +32,6 @@ export class ActionInternal public readonly MenuItem? = this.definition.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - public get contract(): ActionContract { - return this; - } - public execute(context: ActionContext) { return this.definition.execute(context); } @@ -61,23 +55,12 @@ export class ActionInternal if (!this.definition.getHref) return undefined; return this.definition.getHref(context); } - - serialize(): SerializedAction { - const serialized: SerializedAction = { - id: this.id, - type: this.type || '', - }; - - return serialized; - } - - deserialize() {} } export type AnyActionInternal = ActionInternal; export interface SerializedAction { - readonly id: string; readonly type: string; - // readonly state: ActionState; + readonly name: string; + readonly config: Config; } diff --git a/src/plugins/ui_actions/public/actions/action_state_container.ts b/src/plugins/ui_actions/public/actions/action_state_container.ts deleted file mode 100644 index ce571a95acddf..0000000000000 --- a/src/plugins/ui_actions/public/actions/action_state_container.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createStateContainer, StateContainer } from '../../../kibana_utils/common'; - -export interface ActionState { - readonly name: string; - readonly config: Config; -} - -export interface ActionStateTransitions { - setName: (state: ActionState) => (name: string) => ActionState; - setConfig: (state: ActionState) => (config: Config) => ActionState; -} - -export type ActionStateContainer = StateContainer< - ActionState, - ActionStateTransitions, - {} ->; - -export const defaultState: ActionState = { - name: '', - config: {}, -}; - -const pureTransitions: ActionStateTransitions = { - setName: state => name => ({ ...state, name }), - setConfig: state => config => ({ ...state, config }), -}; - -export const createActionStateContainer = ( - state: Partial> -): ActionStateContainer => - createStateContainer({ ...defaultState, ...state } as ActionState, pureTransitions); diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 573eaca74dec2..eb004037926c7 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -19,6 +19,6 @@ export * from './action'; export * from './action_internal'; -export * from './action_contract'; export * from './create_action'; export * from './incompatible_action_error'; +export * from './dynamic_action_storage'; diff --git a/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx b/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx deleted file mode 100644 index f4aaa67d43d76..0000000000000 --- a/src/plugins/ui_actions/public/components/configure_action/configure_action.story.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import useObservable from 'react-use/lib/useObservable'; -import { ConfigureAction } from '.'; -import { createSampleGoToUrlAction } from '../../tests'; -import { ActionInternal } from '../../actions'; - -const action = new ActionInternal(createSampleGoToUrlAction()); -const actionWithPresetConfig = new ActionInternal(createSampleGoToUrlAction()); -actionWithPresetConfig.state.transitions.setConfig({ - url: 'http://google.com', - openInNewTab: true, -}); -const actionMissingCollectConfig = new ActionInternal({ - ...createSampleGoToUrlAction(), - CollectConfig: undefined, -}); - -const DemoDefault: React.FC = () => { - useObservable(action.state.state$); - - return ( -
- -
-
-
-
{JSON.stringify(action.serialize(), null, 4)}
-
- ); -}; - -storiesOf('components/ConfigureAction', module) - .add('default', () => ) - .add('with preset config', () => ) - .add('missing CollectConfig', () => ); diff --git a/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx b/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx deleted file mode 100644 index 16629f4c092da..0000000000000 --- a/src/plugins/ui_actions/public/components/configure_action/configure_action.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiForm } from '@elastic/eui'; -import { AnyActionInternal } from '../../actions'; -import { ErrorConfigureAction } from '../error_configure_action'; -import { txtMissingCollectConfig } from './i18n'; -import { useContainerState } from '../../../../kibana_utils/common'; - -export interface ConfigureActionProps { - context?: unknown; - action: AnyActionInternal; -} - -export const ConfigureAction: React.FC = ({ context, action }) => { - const { config } = useContainerState(action.state); - - if (!action.ReactCollectConfig) { - return ; - } - - return ( - - - - ); -}; diff --git a/src/plugins/ui_actions/public/components/configure_action/i18n.ts b/src/plugins/ui_actions/public/components/configure_action/i18n.ts deleted file mode 100644 index c574aef9c24fe..0000000000000 --- a/src/plugins/ui_actions/public/components/configure_action/i18n.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtMissingCollectConfig = i18n.translate('uiActions.components.missingCollectConfig', { - defaultMessage: 'Dynamic action must have CollectConfig component defined.', -}); diff --git a/src/plugins/ui_actions/public/components/configure_action/index.tsx b/src/plugins/ui_actions/public/components/configure_action/index.tsx deleted file mode 100644 index f876aba7c13ec..0000000000000 --- a/src/plugins/ui_actions/public/components/configure_action/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './configure_action'; diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx index 5d64bbd813164..655302bf54734 100644 --- a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx +++ b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx @@ -20,10 +20,13 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ErrorConfigureAction } from '.'; -import { createSampleGoToUrlAction } from '../../tests'; import { ActionInternal } from '../../actions'; -const action = new ActionInternal(createSampleGoToUrlAction()); +const action = new ActionInternal({ + id: 'TEST', + type: 'TEST_TYPE' as any, + execute: async () => {}, +}); storiesOf('components/ErrorConfigureAction', module) .add('default', () => ) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 08d20acd63ce4..5d4932539e23a 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -32,16 +32,12 @@ export { IncompatibleActionError, ActionDefinition as UiActionsActionDefinition, ActionInternal as UiActionsActionInternal, - ActionContract as UiActionsActionContract, + ActionStorage as UiActionsActionStorage, + SerializedEvent as UiActionsSerializedEvent, + SerializedAction as UiActionsSerializedAction, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; -export { - CollectConfigProps, - Presentable, - Configurable, - ConfigurableBaseConfig, - PresentableBaseContext, -} from './util'; +export { Presentable as UiActionsPresentable } from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 4bba77a4e2303..f86e8a3b45f81 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -26,11 +26,11 @@ import { ActionType, } from '../types'; import { - ActionDefinition, ActionInternal, AnyActionInternal, Action, ActionByType, + AnyActionDefinition, } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -82,7 +82,7 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = >(definition: A) => { + public readonly registerAction = (definition: A) => { if (this.actions.has(definition.id)) { throw new Error(`Action [action.id = ${definition.id}] already registered.`); } @@ -90,9 +90,7 @@ export class UiActionsService { this.actions.set(definition.id, new ActionInternal(definition)); }; - public readonly getAction = >( - id: string - ): ActionInternal => { + public readonly getAction = (id: string): ActionInternal => { if (!this.actions.has(id)) { throw new Error(`Action [action.id = ${id}] not registered.`); } @@ -158,8 +156,7 @@ export class UiActionsService { const actions = actionIds! .map(actionId => this.actions.get(actionId) as AnyActionInternal) - .filter(Boolean) - .map(({ contract }) => contract) as Array>; + .filter(Boolean); return actions as Array>>; }; diff --git a/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx deleted file mode 100644 index 93d8098a7e294..0000000000000 --- a/src/plugins/ui_actions/public/tests/test_samples/go_to_url_action.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiFormRow, EuiFieldText, EuiSwitch } from '@elastic/eui'; -import { ActionDefinition } from '../../actions'; -import { CollectConfigProps } from '../../util'; -import { reactToUiComponent } from '../../../../kibana_react/public'; - -export const SAMPLE_GO_TO_URL_ACTION = 'SAMPLE_GO_TO_URL_ACTION' as ActionDefinition['type']; - -interface Config { - url: string; - openInNewTab: boolean; -} - -const CollectConfig: React.FC> = ({ config, onConfig }) => { - return ( - <> - - onConfig({ ...config, url: event.target.value })} - /> - - - onConfig({ ...config, openInNewTab: !config.openInNewTab })} - /> - - - ); -}; - -export const createSampleGoToUrlAction = (): ActionDefinition => { - return { - type: SAMPLE_GO_TO_URL_ACTION, - id: SAMPLE_GO_TO_URL_ACTION as string, - async execute() {}, - defaultConfig: { - url: '', - openInNewTab: false, - }, - CollectConfig: reactToUiComponent(CollectConfig), - }; -}; diff --git a/src/plugins/ui_actions/public/tests/test_samples/index.ts b/src/plugins/ui_actions/public/tests/test_samples/index.ts index 312ba2353a2a5..dfa71cec89595 100644 --- a/src/plugins/ui_actions/public/tests/test_samples/index.ts +++ b/src/plugins/ui_actions/public/tests/test_samples/index.ts @@ -18,4 +18,3 @@ */ export { createHelloWorldAction } from './hello_world_action'; -export * from './go_to_url_action'; diff --git a/src/plugins/ui_actions/public/util/index.ts b/src/plugins/ui_actions/public/util/index.ts index 0eb3340993f5f..a6943e54f016c 100644 --- a/src/plugins/ui_actions/public/util/index.ts +++ b/src/plugins/ui_actions/public/util/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './configurable'; export * from './presentable'; diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index daaf316cf402d..adf2035940d80 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -52,6 +52,11 @@ export interface Presentable ( - - )) + .add('default', () => ) .add('Only one factory is available', () => ( // to make sure layout doesn't break - + )) .add('Long list of action factories', () => ( // to make sure layout doesn't break )); diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx index aea47be693b8f..cc56714fcb2f8 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.test.tsx @@ -8,21 +8,14 @@ import React from 'react'; import { cleanup, fireEvent, render } from '@testing-library/react/pure'; import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global import { TEST_SUBJ_ACTION_FACTORY_ITEM, TEST_SUBJ_SELECTED_ACTION_FACTORY } from './action_wizard'; -import { - dashboardDrilldownActionFactory, - dashboards, - Demo, - urlDrilldownActionFactory, -} from './test_data'; +import { dashboardFactory, dashboards, Demo, urlFactory } from './test_data'; // TODO: afterEach is not available for it globally during setup // https://github.com/elastic/kibana/issues/59469 afterEach(cleanup); test('Pick and configure action', () => { - const screen = render( - - ); + const screen = render(); // check that all factories are displayed to pick expect(screen.getAllByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).toHaveLength(2); @@ -47,7 +40,7 @@ test('Pick and configure action', () => { }); test('If only one actions factory is available then actionFactory selection is emitted without user input', () => { - const screen = render(); + const screen = render(); // check that no factories are displayed to pick from expect(screen.queryByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).not.toBeInTheDocument(); diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index b88cee84e0e21..3d82d9483d1e9 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -16,30 +16,29 @@ import { } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; -import { - ActionBaseConfig, - ActionFactory, - ActionFactoryList, - ActionFactoryBaseContext, -} from '../../ui_actions_factory'; +import { AnyActionFactory } from '../../services'; import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; +type ActionBaseConfig = object; +type ActionFactoryBaseContext = object; + export interface ActionWizardProps { /** * List of available action factories */ - actionFactories: ActionFactoryList; + actionFactories: AnyActionFactory[]; /** * Currently selected action factory * undefined - is allowed and means that non is selected */ - currentActionFactory?: ActionFactory; + currentActionFactory?: AnyActionFactory; + /** * Action factory selected changed * null - means user click "change" and removed action factory selection */ - onActionFactoryChange: (actionFactory: ActionFactory | null) => void; + onActionFactoryChange: (actionFactory: AnyActionFactory | null) => void; /** * current config for currently selected action factory @@ -99,7 +98,7 @@ export const ActionWizard: React.FC = ({ }; interface SelectedActionFactoryProps { - actionFactory: ActionFactory; + actionFactory: AnyActionFactory; config: ActionBaseConfig; context: ActionFactoryBaseContext; onConfigChange: (config: ActionBaseConfig) => void; @@ -155,9 +154,9 @@ const SelectedActionFactory: React.FC = ({ }; interface ActionFactorySelectorProps { - actionFactories: ActionFactoryList; + actionFactories: AnyActionFactory[]; context: ActionFactoryBaseContext; - onActionFactorySelected: (actionFactory: ActionFactory) => void; + onActionFactorySelected: (actionFactory: AnyActionFactory) => void; } export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index e88399bdc07be..1b1e52f782aca 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -6,10 +6,12 @@ import React, { useState } from 'react'; import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; -import { ActionWizard } from './action_wizard'; -import { ActionBaseConfig, ActionFactory } from '../../ui_actions_factory'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; -import { CollectConfigProps } from '../../../../../../src/plugins/ui_actions/public'; +import { ActionWizard } from './action_wizard'; +import { ActionFactoryDefinition, AnyActionFactory, ActionFactory } from '../../services'; +import { CollectConfigProps } from '../../util'; + +type ActionBaseConfig = object; export const dashboards = [ { id: 'dashboard1', title: 'Dashboard 1' }, @@ -71,7 +73,11 @@ function DashboardDrilldownCollectConfig(props: CollectConfigProps = { +export const dashboardDrilldownActionFactory: ActionFactoryDefinition< + DashboardDrilldownConfig, + any, + any +> = { id: 'Dashboard', getDisplayName: () => 'Go to Dashboard', getIconType: () => 'dashboardApp', @@ -92,8 +98,11 @@ export const dashboardDrilldownActionFactory: ActionFactory null as any, }; +export const dashboardFactory = new ActionFactory(dashboardDrilldownActionFactory); + interface UrlDrilldownConfig { url: string; openInNewTab: boolean; @@ -124,7 +133,7 @@ function UrlDrilldownCollectConfig(props: CollectConfigProps ); } -export const urlDrilldownActionFactory: ActionFactory = { +export const urlDrilldownActionFactory: ActionFactoryDefinition = { id: 'Url', getDisplayName: () => 'Go to URL', getIconType: () => 'link', @@ -144,15 +153,18 @@ export const urlDrilldownActionFactory: ActionFactory = { isCompatible(context?: object): Promise { return Promise.resolve(true); }, + create: () => null as any, }; -export function Demo({ actionFactories }: { actionFactories: Array> }) { +export const urlFactory = new ActionFactory(urlDrilldownActionFactory); + +export function Demo({ actionFactories }: { actionFactories: AnyActionFactory[] }) { const [state, setState] = useState<{ - currentActionFactory?: ActionFactory; + currentActionFactory?: AnyActionFactory; config?: ActionBaseConfig; }>({}); - function changeActionFactory(newActionFactory: ActionFactory | null) { + function changeActionFactory(newActionFactory: AnyActionFactory | null) { if (!newActionFactory) { // removing action factory return setState({}); diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 74dfd3c6012b6..582a5917d2d4a 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -16,8 +16,11 @@ export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; export { ActionWizard } from './components'; export { - ActionFactory, - ActionBaseConfig, - ActionFactoryBaseContext, - ActionFactoryList, -} from './ui_actions_factory'; + ActionFactoryDefinition as AdvancedUiActionsActionFactoryDefinition, + ActionFactory as AdvancedUiActionsActionFactory, + AnyActionFactory as AdvancedUiActionsAnyActionFactory, +} from './services/action_factory_service'; +export { + Configurable as AdvancedUiActionsConfigurable, + CollectConfigProps as AdvancedUiActionsCollectConfigProps, +} from './util'; diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 8acf89fe5a37d..d2df3b7aace69 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -30,7 +30,7 @@ import { TimeBadgeActionContext, } from './custom_time_range_badge'; import { CommonlyUsedRange } from './types'; -import { UiActionsFactoryService } from './ui_actions_factory'; +import { ActionFactoryService } from './services'; interface SetupDependencies { embeddable: IEmbeddableSetup; // Embeddable are needed because they register basic triggers/actions. @@ -43,10 +43,10 @@ interface StartDependencies { } export interface AdvancedUiActionsSetup { - actionFactory: Pick; + actionFactory: Pick; } export interface AdvancedUiActionsStart { - actionFactory: Pick; + actionFactory: Pick; } declare module '../../../../src/plugins/ui_actions/public' { @@ -59,13 +59,13 @@ declare module '../../../../src/plugins/ui_actions/public' { export class AdvancedUiActionsPublicPlugin implements Plugin { - private readonly actionFactoryService = new UiActionsFactoryService(); + private readonly actionFactory = new ActionFactoryService(); constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { uiActions }: SetupDependencies): AdvancedUiActionsSetup { return { - actionFactory: this.actionFactoryService, + actionFactory: this.actionFactory, }; } @@ -90,11 +90,11 @@ export class AdvancedUiActionsPublicPlugin uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge); return { - actionFactory: this.actionFactoryService, + actionFactory: this.actionFactory, }; } public stop() { - this.actionFactoryService.clear(); + this.actionFactory.clear(); } } diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts new file mode 100644 index 0000000000000..5e41c6d3a7fce --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; +import { + UiActionsPresentable, + UiActionsActionDefinition, +} from '../../../../../../src/plugins/ui_actions/public'; +import { + AnyActionFactoryDefinition, + AFDConfig, + AFDFactoryContext, + AFDActionContext, +} from './action_factory_definition'; +import { Configurable } from '../../util'; + +export class ActionFactory + implements UiActionsPresentable>, Configurable> { + constructor(public readonly definition: D) {} + + public readonly id = this.definition.id; + public readonly order = this.definition.order || 0; + public readonly MenuItem? = this.definition.MenuItem; + public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; + + public readonly CollectConfig = this.definition.CollectConfig; + public readonly ReactCollectConfig = uiToReactComponent(this.CollectConfig); + public readonly createConfig = this.definition.createConfig; + public readonly isConfigValid = this.definition.isConfigValid; + + public getIconType(context: AFDFactoryContext): string | undefined { + if (!this.definition.getIconType) return undefined; + return this.definition.getIconType(context); + } + + public getDisplayName(context: AFDFactoryContext): string { + if (!this.definition.getDisplayName) return ''; + return this.definition.getDisplayName(context); + } + + public async isCompatible(context: AFDFactoryContext): Promise { + if (!this.definition.isCompatible) return true; + return await this.definition.isCompatible(context); + } + + public getHref(context: AFDFactoryContext): string | undefined { + if (!this.definition.getHref) return undefined; + return this.definition.getHref(context); + } + + public create(): UiActionsActionDefinition> { + throw new Error('not implemented'); + } +} + +export type AnyActionFactory = ActionFactory; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts new file mode 100644 index 0000000000000..ee03e599a2452 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + UiActionsPresentable, + UiActionsActionDefinition, +} from '../../../../../../src/plugins/ui_actions/public'; +import { Configurable } from '../../util'; + +/** + * This is a convenience interface for registering new action factories. + */ +export interface ActionFactoryDefinition< + Config extends object = object, + FactoryContext extends object = object, + ActionContext extends object = object +> extends Partial>, Configurable { + /** + * Unique ID of the action factory. This ID is used to identify this action + * factory in the registry as well as to construct actions of this ID and + * identify this action factory when presenting it to the user in UI. + */ + id: string; + + /** + * This method should return a definition of a new action, normally used to + * register it in `ui_actions` registry. + */ + create(): UiActionsActionDefinition; +} + +export type AnyActionFactoryDefinition = ActionFactoryDefinition; + +export type AFDConfig = T extends ActionFactoryDefinition + ? Config + : never; + +export type AFDFactoryContext = T extends ActionFactoryDefinition + ? FactoryContext + : never; + +export type AFDActionContext = T extends ActionFactoryDefinition + ? ActionContext + : never; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts new file mode 100644 index 0000000000000..61c8fcc3f7554 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AnyActionFactoryDefinition } from './action_factory_definition'; +import { ActionFactory, AnyActionFactory } from './action_factory'; + +type ActionFactoryRegistry = Map; + +export interface ActionFactoryServiceParams { + actionFactories?: ActionFactoryRegistry; +} + +export class ActionFactoryService { + protected readonly actionFactories: ActionFactoryRegistry; + + constructor({ actionFactories = new Map() }: ActionFactoryServiceParams = {}) { + this.actionFactories = actionFactories; + } + + /** + * Register a new action factory in global registry. + */ + public readonly register = (definition: AnyActionFactoryDefinition) => { + if (this.actionFactories.has(definition.id)) { + throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); + } + + const actionFactory = new ActionFactory(definition); + + this.actionFactories.set(actionFactory.id, actionFactory); + }; + + /** + * Returns an array of all action factories. + */ + public readonly getAll = (): AnyActionFactory[] => { + return [...this.actionFactories.values()]; + }; + + public readonly clear = () => { + this.actionFactories.clear(); + }; +} diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts new file mode 100644 index 0000000000000..f5ee2cfa30191 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './action_factory_definition'; +export * from './action_factory'; +export * from './action_factory_service'; diff --git a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts b/x-pack/plugins/advanced_ui_actions/public/services/index.ts similarity index 84% rename from x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts rename to x-pack/plugins/advanced_ui_actions/public/services/index.ts index be0b69d483037..0f8b4c8d8f409 100644 --- a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './ui_actions_factory_service'; +export * from './action_factory_service'; diff --git a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts b/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts deleted file mode 100644 index 45f5382c9ec1d..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/ui_actions_factory/ui_actions_factory_service.ts +++ /dev/null @@ -1,47 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - Presentable, - PresentableBaseContext, - Configurable, - ConfigurableBaseConfig, -} from '../../../../../src/plugins/ui_actions/public'; - -export type ActionBaseConfig = ConfigurableBaseConfig; -export type ActionFactoryBaseContext = PresentableBaseContext; -export interface ActionFactory< - ActionConfig extends ActionBaseConfig = ActionBaseConfig, - ActionFactoryContext extends ActionFactoryBaseContext = ActionFactoryBaseContext -> extends Presentable, Configurable {} - -export type ActionFactoryList = Array>; - -type ActionFactoryRegistry = Map>; - -export class UiActionsFactoryService { - protected readonly actionFactories: ActionFactoryRegistry; - - constructor({ actionFactories = new Map() }: { actionFactories?: ActionFactoryRegistry } = {}) { - this.actionFactories = actionFactories; - } - - public readonly register = (actionFactory: ActionFactory) => { - if (this.actionFactories.has(actionFactory.id)) { - throw new Error(`ActionFactory [actionFactory.id = ${actionFactory.id}] already registered.`); - } - - this.actionFactories.set(actionFactory.id, actionFactory); - }; - - public readonly getAll = (): Array> => { - return Array.from(this.actionFactories.values()); - }; - - public readonly clear = () => { - this.actionFactories.clear(); - }; -} diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/x-pack/plugins/advanced_ui_actions/public/util/configurable.ts similarity index 57% rename from src/plugins/ui_actions/public/util/configurable.ts rename to x-pack/plugins/advanced_ui_actions/public/util/configurable.ts index d2586db52ec9f..734ccb4147e21 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/x-pack/plugins/advanced_ui_actions/public/util/configurable.ts @@ -1,20 +1,7 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { UiComponent } from 'src/plugins/kibana_utils/common'; diff --git a/x-pack/plugins/advanced_ui_actions/public/util/index.ts b/x-pack/plugins/advanced_ui_actions/public/util/index.ts new file mode 100644 index 0000000000000..f6c5a2c585aaf --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/util/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './configurable'; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index 7031db9987b1b..33feddc538219 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -9,8 +9,8 @@ import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; import { - dashboardDrilldownActionFactory, - urlDrilldownActionFactory, + dashboardFactory, + urlFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; @@ -20,7 +20,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { actionFactory: { getAll: () => { - return [dashboardDrilldownActionFactory, urlDrilldownActionFactory]; + return [dashboardFactory, urlFactory]; }, }, }, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index 540eeb056321e..b0be637965150 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -9,8 +9,8 @@ import { cleanup, fireEvent, render, wait } from '@testing-library/react/pure'; import '@testing-library/jest-dom/extend-expect'; import { createFlyoutManageDrilldowns } from './connected_flyout_manage_drilldowns'; import { - dashboardDrilldownActionFactory, - urlDrilldownActionFactory, + dashboardFactory, + urlFactory, } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; @@ -20,7 +20,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { actionFactory: { getAll: () => { - return [dashboardDrilldownActionFactory, urlDrilldownActionFactory]; + return [dashboardFactory, urlFactory]; }, }, }, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index ef3372deb6429..cd56e9871338a 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -5,7 +5,10 @@ */ import React, { useEffect, useState } from 'react'; -import { ActionFactory, AdvancedUiActionsStart } from '../../../../advanced_ui_actions/public'; +import { + AdvancedUiActionsActionFactory as ActionFactory, + AdvancedUiActionsStart, +} from '../../../../advanced_ui_actions/public'; import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx index f61fbfbb54445..25d172f5f83ce 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -11,25 +11,21 @@ import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; import { FlyoutDrilldownWizard } from '.'; import { - urlDrilldownActionFactory, - dashboardDrilldownActionFactory, + dashboardFactory, + urlFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; storiesOf('components/FlyoutDrilldownWizard', module) .add('default', () => { - return ( - - ); + return ; }) .add('open in flyout - create', () => { return ( {}}> {}} - drilldownActionFactories={[urlDrilldownActionFactory, dashboardDrilldownActionFactory]} + drilldownActionFactories={[urlFactory, dashboardFactory]} /> ); @@ -39,10 +35,10 @@ storiesOf('components/FlyoutDrilldownWizard', module) {}}> {}} - drilldownActionFactories={[urlDrilldownActionFactory, dashboardDrilldownActionFactory]} + drilldownActionFactories={[urlFactory, dashboardFactory]} initialDrilldownWizardConfig={{ name: 'My fancy drilldown', - actionFactory: urlDrilldownActionFactory, + actionFactory: urlFactory, actionConfig: { url: 'https://elastic.co', openInNewTab: true, diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index 8f6b9d4bc97ff..de501aafe90c2 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -15,24 +15,25 @@ import { txtEditDrilldownButtonLabel, txtEditDrilldownTitle, } from './i18n'; +import { DrilldownHelloBar } from '../drilldown_hello_bar'; import { - ActionBaseConfig, - ActionFactory, - ActionFactoryBaseContext, - ActionFactoryList, + AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition, + AdvancedUiActionsActionFactory as ActionFactory, + AdvancedUiActionsAnyActionFactory as AnyActionFactory, } from '../../../../advanced_ui_actions/public'; -import { DrilldownHelloBar } from '../drilldown_hello_bar'; -export interface DrilldownWizardConfig { +type ActionBaseConfig = object; + +export interface DrilldownWizardConfig { name: string; - actionFactory?: ActionFactory; + actionFactory?: ActionFactory>; actionConfig?: ActionConfig; } export interface FlyoutDrilldownWizardProps< CurrentActionConfig extends ActionBaseConfig = ActionBaseConfig > { - drilldownActionFactories: ActionFactoryList; + drilldownActionFactories: AnyActionFactory[]; onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void; onDelete?: () => void; @@ -45,7 +46,7 @@ export interface FlyoutDrilldownWizardProps< showWelcomeMessage?: boolean; onWelcomeHideClick?: () => void; - actionFactoryContext?: ActionFactoryBaseContext; + actionFactoryContext?: object; } export function FlyoutDrilldownWizard< @@ -131,7 +132,7 @@ export function FlyoutDrilldownWizard< } }} actionFactories={drilldownActionFactories} - actionFactoryContext={actionFactoryContext} + actionFactoryContext={actionFactoryContext!} /> {mode === 'edit' && ( <> diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx index dbf7d4c35769c..2fc35eb6b5298 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.story.tsx @@ -13,16 +13,17 @@ const DemoEditName: React.FC = () => { return ( <> -
name: {name}
+ {' '} +
name: {name}
); }; storiesOf('components/FormDrilldownWizard', module) .add('default', () => { - return ; + return ; }) .add('[name=foobar]', () => { - return ; + return ; }) .add('can edit name', () => ); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx index b4707eef79a68..4560773cc8a6d 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.test.tsx @@ -15,14 +15,14 @@ afterEach(cleanup); describe('', () => { test('renders without crashing', () => { const div = document.createElement('div'); - render( {}} />, div); + render( {}} actionFactoryContext={{}} />, div); }); describe('[name=]', () => { test('if name not provided, uses to empty string', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -34,7 +34,7 @@ describe('', () => { test('can set initial name input field value', () => { const div = document.createElement('div'); - render(, div); + render(, div); const input = div.querySelector( '[data-test-subj="dynamicActionNameInput"]' @@ -42,7 +42,7 @@ describe('', () => { expect(input?.value).toBe('foo'); - render(, div); + render(, div); expect(input?.value).toBe('bar'); }); @@ -50,7 +50,7 @@ describe('', () => { test('fires onNameChange callback on name change', () => { const onNameChange = jest.fn(); const utils = renderTestingLibrary( - + ); const input = utils.getByLabelText(txtNameOfDrilldown); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index b6675398a3218..d1dccccf1edb3 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -9,11 +9,8 @@ import './form_drilldown_wizard.scss'; import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; import { - ActionBaseConfig, - ActionFactory, + AdvancedUiActionsAnyActionFactory as AnyActionFactory, ActionWizard, - ActionFactoryList, - ActionFactoryBaseContext, } from '../../../../advanced_ui_actions/public'; const noopFn = () => {}; @@ -22,14 +19,14 @@ export interface FormDrilldownWizardProps { name?: string; onNameChange?: (name: string) => void; - currentActionFactory?: ActionFactory; - onActionFactoryChange?: (actionFactory: ActionFactory | null) => void; - actionFactoryContext?: ActionFactoryBaseContext; + currentActionFactory?: AnyActionFactory; + onActionFactoryChange?: (actionFactory: AnyActionFactory | null) => void; + actionFactoryContext: object; - actionConfig?: ActionBaseConfig; - onActionConfigChange?: (config: ActionBaseConfig) => void; + actionConfig?: object; + onActionConfigChange?: (config: object) => void; - actionFactories?: ActionFactoryList; + actionFactories?: AnyActionFactory[]; } export const FormDrilldownWizard: React.FC = ({ @@ -40,7 +37,7 @@ export const FormDrilldownWizard: React.FC = ({ onActionConfigChange = noopFn, onActionFactoryChange = noopFn, actionFactories = [], - actionFactoryContext = {}, + actionFactoryContext, }) => { const nameFragment = ( diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts index 6a27094b80457..f9667e0baab60 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/service/drilldown_service.ts @@ -5,7 +5,10 @@ */ import { CoreSetup } from 'src/core/public'; -import { AdvancedUiActionsSetup, ActionFactory } from '../../../advanced_ui_actions/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsActionFactoryDefinition, +} from '../../../advanced_ui_actions/public'; // TODO: MOCK DATA import { @@ -22,7 +25,7 @@ export interface DrilldownServiceSetupContract { /** * Convenience method to register a drilldown. */ - registerDrilldown: (drilldownFactory: ActionFactory) => void; + registerDrilldown: (drilldownFactory: AdvancedUiActionsActionFactoryDefinition) => void; } export class DrilldownService { From b8cf696a9fb357e86a5b27a855a5eb3fa66fec53 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 10 Mar 2020 22:26:54 +0100 Subject: [PATCH 14/79] Drilldown registration (#59834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 improve drilldown registration method * fix: 🐛 set up translations for dashboard_enhanced plugin --- .../public/actions/action_definition.ts | 49 +++---------------- .../public/actions/create_action.ts | 2 +- .../ui_actions/public/util/presentable.ts | 4 +- x-pack/.i18nrc.json | 1 + .../advanced_ui_actions/public/index.ts | 1 + .../action_factory_service/action_factory.ts | 24 ++++----- .../action_factory_definition.ts | 8 +-- .../actions/flyout_create_drilldown/index.tsx | 2 +- .../actions/flyout_edit_drilldown/index.tsx | 2 +- x-pack/plugins/drilldowns/public/plugin.ts | 2 +- .../drilldown_service.ts | 38 ++++++++++---- .../public/{service => services}/index.ts | 0 x-pack/plugins/drilldowns/public/types.ts | 33 +++++++++++++ 13 files changed, 91 insertions(+), 75 deletions(-) rename x-pack/plugins/drilldowns/public/{service => services}/drilldown_service.ts (51%) rename x-pack/plugins/drilldowns/public/{service => services}/index.ts (100%) create mode 100644 x-pack/plugins/drilldowns/public/types.ts diff --git a/src/plugins/ui_actions/public/actions/action_definition.ts b/src/plugins/ui_actions/public/actions/action_definition.ts index c590cf8f34ee0..b3456a09879a2 100644 --- a/src/plugins/ui_actions/public/actions/action_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_definition.ts @@ -17,53 +17,16 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; import { ActionType, ActionContextMapping } from '../types'; +import { Presentable } from '../util/presentable'; -export interface ActionDefinition { +export interface ActionDefinition + extends Partial> { /** - * Determined the order when there is more than one action matched to a trigger. - * Higher numbers are displayed first. + * ID of the action factory for this action. Action factories are registered + * int X-Pack `ui_actions` plugin. */ - order?: number; - - /** - * A unique identifier for this action instance. - */ - id?: string; - - /** - * The action type is what determines the context shape. - */ - readonly type: T; - - /** - * Optional EUI icon type that can be displayed along with the title. - */ - getIconType?(context: ActionContextMapping[T]): string; - - /** - * Returns a title to be displayed to the user. - * @param context - */ - getDisplayName?(context: ActionContextMapping[T]): string; - - /** - * `UiComponent` to render when displaying this action as a context menu item. - * If not provided, `getDisplayName` will be used instead. - */ - MenuItem?: UiComponent<{ context: ActionContextMapping[T] }>; - - /** - * Returns a promise that resolves to true if this action is compatible given the context, - * otherwise resolves to false. - */ - isCompatible?(context: ActionContextMapping[T]): Promise; - - /** - * If this returns something truthy, this is used in addition to the `execute` method when clicked. - */ - getHref?(context: ActionContextMapping[T]): string | undefined; + readonly type?: T; /** * Executes the action. diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 90a9415c0b497..462ba966f4715 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -30,5 +30,5 @@ export function createAction(action: ActionDefinition): getDisplayName: () => '', getHref: () => undefined, ...action, - }; + } as ActionByType; } diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index adf2035940d80..945fd2065ce78 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -19,12 +19,10 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; -export type PresentableBaseContext = object; - /** * Represents something that can be displayed to user in UI. */ -export interface Presentable { +export interface Presentable { /** * ID that uniquely identifies this object. */ diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 53628ea970fb6..39fcfe6ddb096 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -9,6 +9,7 @@ "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", + "xpack.dashboard": "plugins/dashboard_enhanced", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.data": "plugins/data_enhanced", "xpack.drilldowns": "plugins/drilldowns", diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 582a5917d2d4a..62b8ef00f2950 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -17,6 +17,7 @@ export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; export { ActionWizard } from './components'; export { ActionFactoryDefinition as AdvancedUiActionsActionFactoryDefinition, + AnyActionFactoryDefinition as AdvancedUiActionsAnyActionFactoryDefinition, ActionFactory as AdvancedUiActionsActionFactory, AnyActionFactory as AdvancedUiActionsAnyActionFactory, } from './services/action_factory_service'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts index 5e41c6d3a7fce..2e9dfa03e5a3d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts @@ -6,19 +6,19 @@ import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; import { - UiActionsPresentable, - UiActionsActionDefinition, + UiActionsPresentable as Presentable, + UiActionsActionDefinition as ActionDefinition, } from '../../../../../../src/plugins/ui_actions/public'; import { AnyActionFactoryDefinition, - AFDConfig, - AFDFactoryContext, - AFDActionContext, + AFDConfig as Config, + AFDFactoryContext as FactoryContext, + AFDActionContext as ActionContext, } from './action_factory_definition'; import { Configurable } from '../../util'; export class ActionFactory - implements UiActionsPresentable>, Configurable> { + implements Presentable>, Configurable> { constructor(public readonly definition: D) {} public readonly id = this.definition.id; @@ -31,28 +31,28 @@ export class ActionFactory public readonly createConfig = this.definition.createConfig; public readonly isConfigValid = this.definition.isConfigValid; - public getIconType(context: AFDFactoryContext): string | undefined { + public getIconType(context: FactoryContext): string | undefined { if (!this.definition.getIconType) return undefined; return this.definition.getIconType(context); } - public getDisplayName(context: AFDFactoryContext): string { + public getDisplayName(context: FactoryContext): string { if (!this.definition.getDisplayName) return ''; return this.definition.getDisplayName(context); } - public async isCompatible(context: AFDFactoryContext): Promise { + public async isCompatible(context: FactoryContext): Promise { if (!this.definition.isCompatible) return true; return await this.definition.isCompatible(context); } - public getHref(context: AFDFactoryContext): string | undefined { + public getHref(context: FactoryContext): string | undefined { if (!this.definition.getHref) return undefined; return this.definition.getHref(context); } - public create(): UiActionsActionDefinition> { - throw new Error('not implemented'); + public create(config: Config): ActionDefinition> { + return this.definition.create(config); } } diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts index ee03e599a2452..655e457b5412a 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts @@ -5,8 +5,8 @@ */ import { - UiActionsPresentable, - UiActionsActionDefinition, + UiActionsPresentable as Presentable, + UiActionsActionDefinition as ActionDefinition, } from '../../../../../../src/plugins/ui_actions/public'; import { Configurable } from '../../util'; @@ -17,7 +17,7 @@ export interface ActionFactoryDefinition< Config extends object = object, FactoryContext extends object = object, ActionContext extends object = object -> extends Partial>, Configurable { +> extends Partial>, Configurable { /** * Unique ID of the action factory. This ID is used to identify this action * factory in the registry as well as to construct actions of this ID and @@ -29,7 +29,7 @@ export interface ActionFactoryDefinition< * This method should return a definition of a new action, normally used to * register it in `ui_actions` registry. */ - create(): UiActionsActionDefinition; + create(config: Config): ActionDefinition; } export type AnyActionFactoryDefinition = ActionFactoryDefinition; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index 459f7f568ea78..94c4830fb8638 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -31,7 +31,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType Promise; } -const displayName = i18n.translate('xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName', { +const displayName = i18n.translate('xpack.dashboard.panel.openFlyoutEditDrilldown.displayName', { defaultMessage: 'Manage drilldowns', }); diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index d2fa88bd21745..ee6f591c1325b 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -7,7 +7,7 @@ import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; -import { DrilldownService, DrilldownServiceSetupContract } from './service'; +import { DrilldownService, DrilldownServiceSetupContract } from './services'; import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; diff --git a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts similarity index 51% rename from x-pack/plugins/drilldowns/public/service/drilldown_service.ts rename to x-pack/plugins/drilldowns/public/services/drilldown_service.ts index f9667e0baab60..8f7ff48270d81 100644 --- a/x-pack/plugins/drilldowns/public/service/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -5,10 +5,8 @@ */ import { CoreSetup } from 'src/core/public'; -import { - AdvancedUiActionsSetup, - AdvancedUiActionsActionFactoryDefinition, -} from '../../../advanced_ui_actions/public'; +import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; +import { Drilldown } from '../types'; // TODO: MOCK DATA import { @@ -25,7 +23,7 @@ export interface DrilldownServiceSetupContract { /** * Convenience method to register a drilldown. */ - registerDrilldown: (drilldownFactory: AdvancedUiActionsActionFactoryDefinition) => void; + registerDrilldown: (drilldown: Drilldown) => void; } export class DrilldownService { @@ -33,12 +31,34 @@ export class DrilldownService { core: CoreSetup, { advancedUiActions }: DrilldownServiceSetupDeps ): DrilldownServiceSetupContract { - const registerDrilldown: DrilldownServiceSetupContract['registerDrilldown'] = drilldownFactory => { - advancedUiActions.actionFactory.register(drilldownFactory); + const registerDrilldown: DrilldownServiceSetupContract['registerDrilldown'] = ({ + id, + places, + CollectConfig, + createConfig, + isConfigValid, + getDisplayName, + getIconType, + execute, + }) => { + advancedUiActions.actionFactory.register({ + id, + CollectConfig, + createConfig, + isConfigValid, + getDisplayName, + getIconType, + isCompatible: async ({ place }: any) => (!places ? true : places.indexOf(place) > -1), + create: config => ({ + id: '', + type: id as any, + execute: async context => await execute(config, context), + }), + }); }; - registerDrilldown(dashboardDrilldownActionFactory); - registerDrilldown(urlDrilldownActionFactory); + registerDrilldown({ ...dashboardDrilldownActionFactory, execute: () => {} } as any); + registerDrilldown({ ...urlDrilldownActionFactory, execute: () => {} } as any); return { registerDrilldown, diff --git a/x-pack/plugins/drilldowns/public/service/index.ts b/x-pack/plugins/drilldowns/public/services/index.ts similarity index 100% rename from x-pack/plugins/drilldowns/public/service/index.ts rename to x-pack/plugins/drilldowns/public/services/index.ts diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts new file mode 100644 index 0000000000000..fe54bcf8a34d8 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../advanced_ui_actions/public'; + +export interface Drilldown< + Config extends object = object, + FactoryContext extends object = object, + ExecutionContext extends object = object +> + extends Pick< + ActionFactoryDefinition, + 'id' | 'createConfig' | 'CollectConfig' | 'isConfigValid' | 'getIconType' | 'getDisplayName' + > { + /** + * List of places where this drilldown should be available, e.g "dashboard". + * If omitted, the drilldown will be show in all places. + */ + places?: string[]; + + /** + * Implements the "navigation" action when user clicks something in the UI and + * instance of this drilldown is triggered. + * + * @param config Config object that user configured this drilldown with. + * @param context Object that represents context in which the underlying + * `UIAction` of this drilldown is being executed in. + */ + execute(config: Config, context: ExecutionContext): void; +} From 2e65fd0ce6e713ea16b5a1ca75616d7b78ab69c4 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 11 Mar 2020 02:45:45 +0100 Subject: [PATCH 15/79] Drilldown events 3 (#59854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add serialize/unserialize to action * feat: 🎸 pass in uiActions service into Embeddable * feat: 🎸 merge ui_actions oss and basic plugins * refactor: 💡 move action factory registry to OSS * fix: 🐛 fix TypeScript errors --- .../public/embeddable/visualize_embeddable.ts | 6 +- .../visualize_embeddable_factory.tsx | 10 ++- .../public/np_ready/public/mocks.ts | 5 +- .../public/np_ready/public/plugin.ts | 4 +- .../public/lib/embeddables/embeddable.tsx | 12 +++- .../public/actions/action_factory.ts | 70 ++++++++++++++++++ .../actions/action_factory_definition.ts | 57 +++++++++++++++ .../public/actions/action_internal.test.ts | 71 +++++++++++++++++++ .../public/actions/action_internal.ts | 40 ++++++++--- .../public/actions/dynamic_action_manager.ts | 30 ++++++++ .../ui_actions/public/actions/index.ts | 2 + src/plugins/ui_actions/public/index.ts | 6 +- src/plugins/ui_actions/public/mocks.ts | 3 + src/plugins/ui_actions/public/plugin.ts | 2 +- .../public/service/ui_actions_service.test.ts | 6 ++ .../public/service/ui_actions_service.ts | 30 ++++++++ src/plugins/ui_actions/public/types.ts | 2 + .../ui_actions}/public/util/configurable.ts | 19 ++++- src/plugins/ui_actions/public/util/index.ts | 1 + .../advanced_ui_actions/public/index.ts | 5 +- .../advanced_ui_actions/public/plugin.ts | 38 +++++----- .../action_factory_service/action_factory.ts | 56 ++------------- .../action_factory_definition.ts | 45 ++---------- .../action_factory_service.ts | 46 ------------ .../services/action_factory_service/index.ts | 1 - .../advanced_ui_actions/public/util/index.ts | 5 +- ...nnected_flyout_manage_drilldowns.story.tsx | 2 +- ...onnected_flyout_manage_drilldowns.test.tsx | 2 +- .../connected_flyout_manage_drilldowns.tsx | 2 +- x-pack/plugins/drilldowns/public/types.ts | 2 +- 30 files changed, 398 insertions(+), 182 deletions(-) create mode 100644 src/plugins/ui_actions/public/actions/action_factory.ts create mode 100644 src/plugins/ui_actions/public/actions/action_factory_definition.ts create mode 100644 src/plugins/ui_actions/public/actions/dynamic_action_manager.ts rename {x-pack/plugins/advanced_ui_actions => src/plugins/ui_actions}/public/util/configurable.ts (57%) delete mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 7525345ccfe1b..e21160beab8db 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -48,6 +48,7 @@ import { buildPipeline } from '../legacy/build_pipeline'; import { Vis } from '../vis'; import { getExpressions, getUiActions } from '../services'; import { VisSavedObject } from '../types'; +import { VisualizationsStartDeps } from '../plugin'; const getKeys = (o: T): Array => Object.keys(o) as Array; @@ -58,6 +59,7 @@ export interface VisualizeEmbeddableConfiguration { editable: boolean; appState?: { save(): void }; uiState?: PersistedState; + uiActions?: VisualizationsStartDeps['uiActions']; } export interface VisualizeInput extends EmbeddableInput { @@ -107,6 +109,7 @@ export class VisualizeEmbeddable extends Embeddable { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - constructor() { + constructor( + private readonly getServices: () => Promise< + [unknown, Pick] + > + ) { super({ savedObjectMetaData: { name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), @@ -114,6 +119,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const indexPattern = await getIndexPattern(savedObject); const indexPatterns = indexPattern ? [indexPattern] : []; + const [, { uiActions }] = await this.getServices(); + return new VisualizeEmbeddable( getTimeFilter(), { @@ -123,6 +130,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< editable: this.isEditable(), appState: input.appState, uiState: input.uiState, + uiActions, }, input, parent diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 9e8eac08c33ea..53e724d72549c 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../../../core/public'; +import { CoreSetup, PluginInitializerContext } from '../../../../../../core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; @@ -26,6 +26,7 @@ import { expressionsPluginMock } from '../../../../../../plugins/expressions/pub import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks'; +import { VisualizationsStartDeps } from './plugin'; const createSetupContract = (): VisualizationsSetup => ({ createBaseVisualization: jest.fn(), @@ -46,7 +47,7 @@ const createStartContract = (): VisualizationsStart => ({ const createInstance = async () => { const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup(), { + const setup = plugin.setup(coreMock.createSetup() as CoreSetup, { data: dataPluginMock.createSetupContract(), expressions: expressionsPluginMock.createSetupContract(), embeddable: embeddablePluginMock.createStartContract(), diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index b8db611f30815..a256faa7aab39 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -109,7 +109,7 @@ export class VisualizationsPlugin constructor(initializerContext: PluginInitializerContext) {} public setup( - core: CoreSetup, + core: CoreSetup, { expressions, embeddable, usageCollection, data }: VisualizationsSetupDeps ): VisualizationsSetup { setUISettings(core.uiSettings); @@ -118,7 +118,7 @@ export class VisualizationsPlugin expressions.registerFunction(visualizationFunction); expressions.registerRenderer(visualizationRenderer); - const embeddableFactory = new VisualizeEmbeddableFactory(); + const embeddableFactory = new VisualizeEmbeddableFactory(core.getStartServices); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); return { diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index a1b332bb65617..5fa8ac19011af 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -23,11 +23,16 @@ import { IContainer } from '../containers'; import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; import { ViewMode } from '../types'; import { EmbeddableActionStorage } from './embeddable_action_storage'; +import { UiActionsStart } from '../ui_actions'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; } +export interface EmbeddableParams { + uiActions?: UiActionsStart; +} + export abstract class Embeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput @@ -55,7 +60,12 @@ export abstract class Embeddable< return this.__actionStorage || (this.__actionStorage = new EmbeddableActionStorage(this)); } - constructor(input: TEmbeddableInput, output: TEmbeddableOutput, parent?: IContainer) { + constructor( + input: TEmbeddableInput, + output: TEmbeddableOutput, + parent?: IContainer, + public readonly params: EmbeddableParams = {} + ) { this.id = input.id; this.output = { title: getPanelTitle(input, output), diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/src/plugins/ui_actions/public/actions/action_factory.ts new file mode 100644 index 0000000000000..2502fe52f7da6 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_factory.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uiToReactComponent } from '../../../kibana_react/public'; +import { Presentable } from '../util/presentable'; +import { ActionDefinition } from './action_definition'; +import { + AnyActionFactoryDefinition, + AFDConfig as Config, + AFDFactoryContext as FactoryContext, + AFDActionContext as ActionContext, +} from './action_factory_definition'; +import { Configurable } from '../util'; + +export class ActionFactory + implements Presentable>, Configurable> { + constructor(public readonly definition: D) {} + + public readonly id = this.definition.id; + public readonly order = this.definition.order || 0; + public readonly MenuItem? = this.definition.MenuItem; + public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; + + public readonly CollectConfig = this.definition.CollectConfig; + public readonly ReactCollectConfig = uiToReactComponent(this.CollectConfig); + public readonly createConfig = this.definition.createConfig; + public readonly isConfigValid = this.definition.isConfigValid; + + public getIconType(context: FactoryContext): string | undefined { + if (!this.definition.getIconType) return undefined; + return this.definition.getIconType(context); + } + + public getDisplayName(context: FactoryContext): string { + if (!this.definition.getDisplayName) return ''; + return this.definition.getDisplayName(context); + } + + public async isCompatible(context: FactoryContext): Promise { + if (!this.definition.isCompatible) return true; + return await this.definition.isCompatible(context); + } + + public getHref(context: FactoryContext): string | undefined { + if (!this.definition.getHref) return undefined; + return this.definition.getHref(context); + } + + public create(config: Config): ActionDefinition> { + return this.definition.create(config); + } +} + +export type AnyActionFactory = ActionFactory; diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts new file mode 100644 index 0000000000000..d26d754d56d50 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/action_factory_definition.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ActionDefinition } from './action_definition'; +import { Presentable, Configurable } from '../util'; + +/** + * This is a convenience interface for registering new action factories. + */ +export interface ActionFactoryDefinition< + Config extends object = object, + FactoryContext extends object = object, + ActionContext extends object = object +> extends Partial>, Configurable { + /** + * Unique ID of the action factory. This ID is used to identify this action + * factory in the registry as well as to construct actions of this ID and + * identify this action factory when presenting it to the user in UI. + */ + id: string; + + /** + * This method should return a definition of a new action, normally used to + * register it in `ui_actions` registry. + */ + create(config: Config): ActionDefinition; // TODO: FIX THIS.... +} + +export type AnyActionFactoryDefinition = ActionFactoryDefinition; + +export type AFDConfig = T extends ActionFactoryDefinition + ? Config + : never; + +export type AFDFactoryContext = T extends ActionFactoryDefinition + ? FactoryContext + : never; + +export type AFDActionContext = T extends ActionFactoryDefinition + ? ActionContext + : never; diff --git a/src/plugins/ui_actions/public/actions/action_internal.test.ts b/src/plugins/ui_actions/public/actions/action_internal.test.ts index b14346180c274..2a2b639434ac2 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.test.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.test.ts @@ -30,4 +30,75 @@ describe('ActionInternal', () => { const action = new ActionInternal(defaultActionDef); expect(action.id).toBe('test-action'); }); + + describe('serialize()', () => { + test('can serialize very simple action', () => { + const action = new ActionInternal(defaultActionDef); + + action.config = {}; + + const serialized = action.serialize(); + + expect(serialized).toMatchObject({ + id: 'test-action', + name: '', + config: expect.any(Object), + }); + }); + + test('can serialize action with modified state', () => { + const action = new ActionInternal({ + ...defaultActionDef, + type: 'ACTION_TYPE' as any, + order: 11, + }); + + action.name = 'qux'; + action.config = { foo: 'bar' }; + + const serialized = action.serialize(); + + expect(serialized).toMatchObject({ + id: 'test-action', + factoryId: 'ACTION_TYPE', + name: 'qux', + config: { + foo: 'bar', + }, + }); + }); + }); + + describe('deserialize', () => { + const serialized = { + id: 'id', + factoryId: 'type', + name: 'name', + config: { + foo: 'foo', + }, + }; + + test('can deserialize action state', () => { + const action = new ActionInternal({ + ...defaultActionDef, + }); + + action.deserialize(serialized); + + expect(action.name).toBe('name'); + expect(action.config).toMatchObject(serialized.config); + }); + + test('does not overwrite action id and type', () => { + const action = new ActionInternal({ + ...defaultActionDef, + }); + + action.deserialize(serialized); + + expect(action.id).toBe('test-action'); + expect(action.type).toBe(''); + }); + }); }); diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index d545757108eaa..8cf120a2899c6 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -17,13 +17,13 @@ * under the License. */ -import { Action, ActionContext, AnyActionDefinition } from './action'; +import { Action, ActionContext as Context, AnyActionDefinition } from './action'; import { Presentable } from '../util/presentable'; import { uiToReactComponent } from '../../../kibana_react/public'; import { ActionType } from '../types'; export class ActionInternal
- implements Action>, Presentable> { + implements Action>, Presentable> { constructor(public readonly definition: A) {} public readonly id: string = this.definition.id; @@ -32,35 +32,59 @@ export class ActionInternal public readonly MenuItem? = this.definition.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - public execute(context: ActionContext) { + public name: string = ''; + public config?: object; + + public execute(context: Context) { return this.definition.execute(context); } - public getIconType(context: ActionContext): string | undefined { + public getIconType(context: Context): string | undefined { if (!this.definition.getIconType) return undefined; return this.definition.getIconType(context); } - public getDisplayName(context: ActionContext): string { + public getDisplayName(context: Context): string { if (!this.definition.getDisplayName) return ''; return this.definition.getDisplayName(context); } - public async isCompatible(context: ActionContext): Promise { + public async isCompatible(context: Context): Promise { if (!this.definition.isCompatible) return true; return await this.definition.isCompatible(context); } - public getHref(context: ActionContext): string | undefined { + public getHref(context: Context): string | undefined { if (!this.definition.getHref) return undefined; return this.definition.getHref(context); } + + public serialize() { + if (!this.config) { + throw new Error('Action does not have a config.'); + } + + const serialized: SerializedAction = { + id: this.id, + factoryId: this.type, + name: this.name, + config: this.config, + }; + + return serialized; + } + + public deserialize({ name, config }: SerializedAction) { + this.name = name; + this.config = config; + } } export type AnyActionInternal = ActionInternal; export interface SerializedAction { - readonly type: string; + readonly id: string; + readonly factoryId: string; readonly name: string; readonly config: Config; } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts new file mode 100644 index 0000000000000..563de3db13b50 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ActionStorage } from './dynamic_action_storage'; +import { UiActionsService } from '../service'; + +export interface DynamicActionManagerParams { + storage: ActionStorage; + uiActions: UiActionsService; +} + +export class DynamicActionManager { + constructor(protected readonly params: DynamicActionManagerParams) {} +} diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index eb004037926c7..f2c105bca9d45 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -19,6 +19,8 @@ export * from './action'; export * from './action_internal'; +export * from './action_factory_definition'; +export * from './action_factory'; export * from './create_action'; export * from './incompatible_action_error'; export * from './dynamic_action_storage'; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 5d4932539e23a..e27cc04a7d759 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -37,7 +37,11 @@ export { SerializedAction as UiActionsSerializedAction, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; -export { Presentable as UiActionsPresentable } from './util'; +export { + Presentable as UiActionsPresentable, + Configurable as UiActionsConfigurable, + CollectConfigProps as UiActionsCollectConfigProps, +} from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index c1be6b2626525..786ee4b5bd025 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -32,6 +32,7 @@ const createSetupContract = (): Setup => { detachAction: jest.fn(), registerAction: jest.fn(), registerTrigger: jest.fn(), + registerActionFactory: jest.fn(), }; return setupContract; }; @@ -41,9 +42,11 @@ const createStartContract = (): Start => { attachAction: jest.fn(), registerAction: jest.fn(), registerTrigger: jest.fn(), + registerActionFactory: jest.fn(), getAction: jest.fn(), detachAction: jest.fn(), executeTriggerActions: jest.fn(), + getActionFactories: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index 0874803db7d37..8d0ff41b6acde 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -22,7 +22,7 @@ import { UiActionsService } from './service'; export type UiActionsSetup = Pick< UiActionsService, - 'attachAction' | 'detachAction' | 'registerAction' | 'registerTrigger' + 'attachAction' | 'detachAction' | 'registerAction' | 'registerTrigger' | 'registerActionFactory' >; export type UiActionsStart = PublicMethodsOf; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index c9395e15854ec..65a18f0460f22 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -476,4 +476,10 @@ describe('UiActionsService', () => { ); }); }); + + describe('action factories', () => { + test.todo('.getActionFactories() returns empty array if no action factories registered'); + test.todo('can register an action factory'); + test.todo('can retrieve all action factories'); + }); }); diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index f86e8a3b45f81..9dc463123e6af 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -24,6 +24,7 @@ import { TriggerId, TriggerContextMapping, ActionType, + ActionFactoryRegistry, } from '../types'; import { ActionInternal, @@ -31,6 +32,9 @@ import { Action, ActionByType, AnyActionDefinition, + AnyActionFactoryDefinition, + ActionFactory, + AnyActionFactory, } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -44,21 +48,25 @@ export interface UiActionsServiceParams { * A 1-to-N mapping from `Trigger` to zero or more `Action`. */ readonly triggerToActions?: TriggerToActionsRegistry; + readonly actionFactories?: ActionFactoryRegistry; } export class UiActionsService { protected readonly triggers: TriggerRegistry; protected readonly actions: ActionRegistry; protected readonly triggerToActions: TriggerToActionsRegistry; + protected readonly actionFactories: ActionFactoryRegistry; constructor({ triggers = new Map(), actions = new Map(), triggerToActions = new Map(), + actionFactories = new Map(), }: UiActionsServiceParams = {}) { this.triggers = triggers; this.actions = actions; this.triggerToActions = triggerToActions; + this.actionFactories = actionFactories; } public readonly registerTrigger = (trigger: Trigger) => { @@ -194,6 +202,7 @@ export class UiActionsService { this.actions.clear(); this.triggers.clear(); this.triggerToActions.clear(); + this.actionFactories.clear(); }; /** @@ -213,4 +222,25 @@ export class UiActionsService { return new UiActionsService({ triggers, actions, triggerToActions }); }; + + /** + * Register an action factory. Action factories are used to configure and + * serialize/deserialize dynamic actions. + */ + public readonly registerActionFactory = (definition: AnyActionFactoryDefinition) => { + if (this.actionFactories.has(definition.id)) { + throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); + } + + const actionFactory = new ActionFactory(definition); + + this.actionFactories.set(actionFactory.id, actionFactory); + }; + + /** + * Returns an array of all action factories. + */ + public readonly getActionFactories = (): AnyActionFactory[] => { + return [...this.actionFactories.values()]; + }; } diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 51990d8158d7a..c2e5a1668f7f4 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -19,10 +19,12 @@ import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; +import { AnyActionFactory } from './actions/action_factory'; export type TriggerRegistry = Map>; export type ActionRegistry = Map>; export type TriggerToActionsRegistry = Map; +export type ActionFactoryRegistry = Map; const DEFAULT_TRIGGER = ''; diff --git a/x-pack/plugins/advanced_ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts similarity index 57% rename from x-pack/plugins/advanced_ui_actions/public/util/configurable.ts rename to src/plugins/ui_actions/public/util/configurable.ts index 734ccb4147e21..d2586db52ec9f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { UiComponent } from 'src/plugins/kibana_utils/common'; diff --git a/src/plugins/ui_actions/public/util/index.ts b/src/plugins/ui_actions/public/util/index.ts index a6943e54f016c..53c6109cac4ca 100644 --- a/src/plugins/ui_actions/public/util/index.ts +++ b/src/plugins/ui_actions/public/util/index.ts @@ -18,3 +18,4 @@ */ export * from './presentable'; +export * from './configurable'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 62b8ef00f2950..cbe7b0c26db3b 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -12,7 +12,10 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { AdvancedUiActionsPublicPlugin as Plugin }; -export { AdvancedUiActionsSetup, AdvancedUiActionsStart } from './plugin'; +export { + SetupContract as AdvancedUiActionsSetup, + StartContract as AdvancedUiActionsStart, +} from './plugin'; export { ActionWizard } from './components'; export { diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index d2df3b7aace69..3d6aee460be1e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -11,7 +11,7 @@ import { Plugin, } from '../../../../src/core/public'; import { createReactOverlays } from '../../../../src/plugins/kibana_react/public'; -import { UiActionsStart, UiActionsSetup } from '../../../../src/plugins/ui_actions/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, @@ -30,7 +30,6 @@ import { TimeBadgeActionContext, } from './custom_time_range_badge'; import { CommonlyUsedRange } from './types'; -import { ActionFactoryService } from './services'; interface SetupDependencies { embeddable: IEmbeddableSetup; // Embeddable are needed because they register basic triggers/actions. @@ -42,11 +41,15 @@ interface StartDependencies { uiActions: UiActionsStart; } -export interface AdvancedUiActionsSetup { - actionFactory: Pick; +export interface SetupContract extends UiActionsSetup { + actionFactory: { + register: UiActionsSetup['registerActionFactory']; + }; } -export interface AdvancedUiActionsStart { - actionFactory: Pick; +export interface StartContract extends UiActionsStart { + actionFactory: { + getAll: UiActionsStart['getActionFactories']; + }; } declare module '../../../../src/plugins/ui_actions/public' { @@ -57,19 +60,19 @@ declare module '../../../../src/plugins/ui_actions/public' { } export class AdvancedUiActionsPublicPlugin - implements - Plugin { - private readonly actionFactory = new ActionFactoryService(); - + implements Plugin { constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { uiActions }: SetupDependencies): AdvancedUiActionsSetup { + public setup(core: CoreSetup, { uiActions }: SetupDependencies): SetupContract { return { - actionFactory: this.actionFactory, + ...uiActions, + actionFactory: { + register: uiActions.registerActionFactory, + }, }; } - public start(core: CoreStart, { uiActions }: StartDependencies): AdvancedUiActionsStart { + public start(core: CoreStart, { uiActions }: StartDependencies): StartContract { const dateFormat = core.uiSettings.get('dateFormat') as string; const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[]; const { openModal } = createReactOverlays(core); @@ -90,11 +93,12 @@ export class AdvancedUiActionsPublicPlugin uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge); return { - actionFactory: this.actionFactory, + ...uiActions, + actionFactory: { + getAll: uiActions.getActionFactories, + }, }; } - public stop() { - this.actionFactory.clear(); - } + public stop() {} } diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts index 2e9dfa03e5a3d..955b5c0d70b52 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts @@ -4,56 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; -import { - UiActionsPresentable as Presentable, - UiActionsActionDefinition as ActionDefinition, -} from '../../../../../../src/plugins/ui_actions/public'; -import { - AnyActionFactoryDefinition, - AFDConfig as Config, - AFDFactoryContext as FactoryContext, - AFDActionContext as ActionContext, -} from './action_factory_definition'; -import { Configurable } from '../../util'; +/* eslint-disable */ -export class ActionFactory - implements Presentable>, Configurable> { - constructor(public readonly definition: D) {} - - public readonly id = this.definition.id; - public readonly order = this.definition.order || 0; - public readonly MenuItem? = this.definition.MenuItem; - public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - - public readonly CollectConfig = this.definition.CollectConfig; - public readonly ReactCollectConfig = uiToReactComponent(this.CollectConfig); - public readonly createConfig = this.definition.createConfig; - public readonly isConfigValid = this.definition.isConfigValid; - - public getIconType(context: FactoryContext): string | undefined { - if (!this.definition.getIconType) return undefined; - return this.definition.getIconType(context); - } - - public getDisplayName(context: FactoryContext): string { - if (!this.definition.getDisplayName) return ''; - return this.definition.getDisplayName(context); - } - - public async isCompatible(context: FactoryContext): Promise { - if (!this.definition.isCompatible) return true; - return await this.definition.isCompatible(context); - } - - public getHref(context: FactoryContext): string | undefined { - if (!this.definition.getHref) return undefined; - return this.definition.getHref(context); - } - - public create(config: Config): ActionDefinition> { - return this.definition.create(config); - } -} - -export type AnyActionFactory = ActionFactory; +export { + ActionFactory, AnyActionFactory +} from '../../../../../../src/plugins/ui_actions/public/actions/action_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts index 655e457b5412a..f29abe6e41b22 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts @@ -4,44 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - UiActionsPresentable as Presentable, - UiActionsActionDefinition as ActionDefinition, -} from '../../../../../../src/plugins/ui_actions/public'; -import { Configurable } from '../../util'; +/* eslint-disable */ -/** - * This is a convenience interface for registering new action factories. - */ -export interface ActionFactoryDefinition< - Config extends object = object, - FactoryContext extends object = object, - ActionContext extends object = object -> extends Partial>, Configurable { - /** - * Unique ID of the action factory. This ID is used to identify this action - * factory in the registry as well as to construct actions of this ID and - * identify this action factory when presenting it to the user in UI. - */ - id: string; - - /** - * This method should return a definition of a new action, normally used to - * register it in `ui_actions` registry. - */ - create(config: Config): ActionDefinition; -} - -export type AnyActionFactoryDefinition = ActionFactoryDefinition; - -export type AFDConfig = T extends ActionFactoryDefinition - ? Config - : never; - -export type AFDFactoryContext = T extends ActionFactoryDefinition - ? FactoryContext - : never; - -export type AFDActionContext = T extends ActionFactoryDefinition - ? ActionContext - : never; +export { + ActionFactoryDefinition, + AnyActionFactoryDefinition, +} from '../../../../../../src/plugins/ui_actions/public/actions/action_factory_definition'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts deleted file mode 100644 index 61c8fcc3f7554..0000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_service.ts +++ /dev/null @@ -1,46 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AnyActionFactoryDefinition } from './action_factory_definition'; -import { ActionFactory, AnyActionFactory } from './action_factory'; - -type ActionFactoryRegistry = Map; - -export interface ActionFactoryServiceParams { - actionFactories?: ActionFactoryRegistry; -} - -export class ActionFactoryService { - protected readonly actionFactories: ActionFactoryRegistry; - - constructor({ actionFactories = new Map() }: ActionFactoryServiceParams = {}) { - this.actionFactories = actionFactories; - } - - /** - * Register a new action factory in global registry. - */ - public readonly register = (definition: AnyActionFactoryDefinition) => { - if (this.actionFactories.has(definition.id)) { - throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); - } - - const actionFactory = new ActionFactory(definition); - - this.actionFactories.set(actionFactory.id, actionFactory); - }; - - /** - * Returns an array of all action factories. - */ - public readonly getAll = (): AnyActionFactory[] => { - return [...this.actionFactories.values()]; - }; - - public readonly clear = () => { - this.actionFactories.clear(); - }; -} diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts index f5ee2cfa30191..db5bb3aa62a16 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts @@ -6,4 +6,3 @@ export * from './action_factory_definition'; export * from './action_factory'; -export * from './action_factory_service'; diff --git a/x-pack/plugins/advanced_ui_actions/public/util/index.ts b/x-pack/plugins/advanced_ui_actions/public/util/index.ts index f6c5a2c585aaf..fd3ab89973348 100644 --- a/x-pack/plugins/advanced_ui_actions/public/util/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/util/index.ts @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './configurable'; +export { + UiActionsConfigurable as Configurable, + UiActionsCollectConfigProps as CollectConfigProps, +} from '../../../../../src/plugins/ui_actions/public'; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index 33feddc538219..ae9dbfb359a7b 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -23,7 +23,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ return [dashboardFactory, urlFactory]; }, }, - }, + } as any, storage: new Storage(new StubBrowserStorage()), }); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index b0be637965150..b19a69104ee73 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -23,7 +23,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ return [dashboardFactory, urlFactory]; }, }, - }, + } as any, storage, }); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index cd56e9871338a..f95c176c52155 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -36,7 +36,7 @@ export function createFlyoutManageDrilldowns({ storage: IStorageWrapper; }) { // This is ok to assume this is static, - // because all action factories should be registerd in setup phase + // because all action factories should be registered in setup phase const allActionFactories = advancedUiActions.actionFactory.getAll(); return (props: ConnectedFlyoutManageDrilldownsProps) => { diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index fe54bcf8a34d8..7904a1969d2dc 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -17,7 +17,7 @@ export interface Drilldown< > { /** * List of places where this drilldown should be available, e.g "dashboard". - * If omitted, the drilldown will be show in all places. + * If omitted, the drilldown will be shown in all places. */ places?: string[]; From beb053b22392196951125ab1544404367634c74d Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 11 Mar 2020 11:01:04 +0100 Subject: [PATCH 16/79] Drilldown events 4 (#59876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 mock sample drilldown execute methods * feat: 🎸 add .dynamicActions manager to Embeddable * feat: 🎸 add first version of dynamic action manager --- .../public/embeddable/visualize_embeddable.ts | 1 + .../public/lib/embeddables/embeddable.tsx | 25 +++++-- .../public/lib/embeddables/i_embeddable.ts | 6 ++ .../public/actions/action_factory.ts | 9 ++- .../actions/action_factory_definition.ts | 7 +- .../public/actions/action_internal.ts | 17 ++--- .../public/actions/create_action.ts | 13 ++-- .../public/actions/dynamic_action_manager.ts | 67 ++++++++++++++++++- .../public/actions/dynamic_action_storage.ts | 4 +- .../ui_actions/public/actions/index.ts | 2 + .../{action_definition.ts => types.ts} | 19 ++---- src/plugins/ui_actions/public/index.ts | 2 +- .../public/service/ui_actions_service.test.ts | 4 ++ .../public/service/ui_actions_service.ts | 20 +++++- .../components/action_wizard/test_data.tsx | 5 +- .../public/services/drilldown_service.ts | 10 ++- 16 files changed, 160 insertions(+), 51 deletions(-) rename src/plugins/ui_actions/public/actions/{action_definition.ts => types.ts} (62%) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index e21160beab8db..7e5ed61597877 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -304,6 +304,7 @@ export class VisualizeEmbeddable extends Embeddable true, + storage: new EmbeddableActionStorage(this), + uiActions: this.params.uiActions, + }); + } + + return this.__dynamicActions; } constructor( @@ -66,6 +78,7 @@ export abstract class Embeddable< parent?: IContainer, public readonly params: EmbeddableParams = {} ) { + window.emb = this; this.id = input.id; this.output = { title: getPanelTitle(input, output), @@ -89,6 +102,10 @@ export abstract class Embeddable< this.onResetInput(newInput); }); } + + if (this.dynamicActions) { + this.dynamicActions.start(); + } } public getIsContainer(): this is IContainer { diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 62121cb0f23dd..7b1e3076142bd 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -18,6 +18,7 @@ */ import { Observable } from 'rxjs'; +import { UiActionsDynamicActionManager } from '../../../../../plugins/ui_actions/public'; import { Adapters } from '../types'; import { IContainer } from '../containers/i_container'; import { ViewMode } from '../types'; @@ -82,6 +83,11 @@ export interface IEmbeddable< **/ readonly id: string; + /** + * Default implementation of dynamic action API for embeddables. + */ + dynamicActions?: UiActionsDynamicActionManager; + /** * A functional representation of the isContainer variable, but helpful for typescript to * know the shape if this returns true diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/src/plugins/ui_actions/public/actions/action_factory.ts index 2502fe52f7da6..3f00121a9c577 100644 --- a/src/plugins/ui_actions/public/actions/action_factory.ts +++ b/src/plugins/ui_actions/public/actions/action_factory.ts @@ -19,7 +19,7 @@ import { uiToReactComponent } from '../../../kibana_react/public'; import { Presentable } from '../util/presentable'; -import { ActionDefinition } from './action_definition'; +import { ActionDefinition } from './action'; import { AnyActionFactoryDefinition, AFDConfig as Config, @@ -27,6 +27,7 @@ import { AFDActionContext as ActionContext, } from './action_factory_definition'; import { Configurable } from '../util'; +import { SerializedAction } from './types'; export class ActionFactory implements Presentable>, Configurable> { @@ -62,8 +63,10 @@ export class ActionFactory return this.definition.getHref(context); } - public create(config: Config): ActionDefinition> { - return this.definition.create(config); + public create( + serializedAction: Omit>, 'factoryId'> + ): ActionDefinition> { + return this.definition.create(serializedAction); } } diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts index d26d754d56d50..eaeca9fd4383e 100644 --- a/src/plugins/ui_actions/public/actions/action_factory_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_factory_definition.ts @@ -17,8 +17,9 @@ * under the License. */ -import { ActionDefinition } from './action_definition'; +import { ActionDefinition } from './action'; import { Presentable, Configurable } from '../util'; +import { SerializedAction } from './types'; /** * This is a convenience interface for registering new action factories. @@ -39,7 +40,9 @@ export interface ActionFactoryDefinition< * This method should return a definition of a new action, normally used to * register it in `ui_actions` registry. */ - create(config: Config): ActionDefinition; // TODO: FIX THIS.... + create( + serializedAction: Omit, 'factoryId'> + ): ActionDefinition; } export type AnyActionFactoryDefinition = ActionFactoryDefinition; diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index 8cf120a2899c6..ef27b78464f2b 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -21,6 +21,7 @@ import { Action, ActionContext as Context, AnyActionDefinition } from './action' import { Presentable } from '../util/presentable'; import { uiToReactComponent } from '../../../kibana_react/public'; import { ActionType } from '../types'; +import { SerializedAction } from './types'; export class ActionInternal implements Action>, Presentable> { @@ -45,7 +46,7 @@ export class ActionInternal } public getDisplayName(context: Context): string { - if (!this.definition.getDisplayName) return ''; + if (!this.definition.getDisplayName) return `Action: ${this.id}`; return this.definition.getDisplayName(context); } @@ -64,8 +65,7 @@ export class ActionInternal throw new Error('Action does not have a config.'); } - const serialized: SerializedAction = { - id: this.id, + const serialized: SerializedAction = { factoryId: this.type, name: this.name, config: this.config, @@ -74,17 +74,10 @@ export class ActionInternal return serialized; } - public deserialize({ name, config }: SerializedAction) { + public deserialize({ name, config }: SerializedAction) { this.name = name; - this.config = config; + this.config = config as object; } } export type AnyActionInternal = ActionInternal; - -export interface SerializedAction { - readonly id: string; - readonly factoryId: string; - readonly name: string; - readonly config: Config; -} diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 462ba966f4715..5b10a8dc83a62 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -17,11 +17,14 @@ * under the License. */ -import { ActionByType } from './action'; -import { ActionType } from '../types'; -import { ActionDefinition } from './action_definition'; +import { Ensure } from '@kbn/utility-types'; +import { Action } from './action'; +import { TriggerContextMapping } from '../types'; +import { ActionDefinition } from './action'; -export function createAction(action: ActionDefinition): ActionByType { +export function createAction( + action: ActionDefinition> +): Action { return { getIconType: () => undefined, order: 0, @@ -30,5 +33,5 @@ export function createAction(action: ActionDefinition): getDisplayName: () => '', getHref: () => undefined, ...action, - } as ActionByType; + } as Action; } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 563de3db13b50..d48d78e896c9a 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -17,14 +17,77 @@ * under the License. */ -import { ActionStorage } from './dynamic_action_storage'; +import { v4 as uuidv4 } from 'uuid'; +import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; import { UiActionsService } from '../service'; +import { SerializedAction } from './types'; +import { ActionDefinition } from './action'; export interface DynamicActionManagerParams { storage: ActionStorage; - uiActions: UiActionsService; + uiActions: Pick; + isCompatible: (context: C) => Promise; } export class DynamicActionManager { + static idPrefixCounter = 0; + + private readonly idPrefix = 'DYN_ACTION_' + DynamicActionManager.idPrefixCounter++; + constructor(protected readonly params: DynamicActionManagerParams) {} + + protected generateActionId(eventId: string): string { + return this.idPrefix + eventId; + } + + public async start() { + const events = await this.params.storage.list(); + + for (const event of events) { + this.reviveAction(event); + } + } + + public async stop() { + /* + const { storage, uiActions } = this.params; + const events = await storage.list(); + + for (const event of events) { + uiActions.detachAction(event.triggerId, event.action.id); + uiActions.unregisterAction(event.action.id); + } + */ + } + + public async createEvent(action: SerializedAction, triggerId = 'VALUE_CLICK_TRIGGER') { + const event: SerializedEvent = { + eventId: uuidv4(), + triggerId, + action, + }; + + await this.params.storage.create(event); + this.reviveAction(event); + } + + protected reviveAction(event: SerializedEvent) { + const { eventId, triggerId, action } = event; + const { uiActions, isCompatible } = this.params; + const { name } = action; + + const actionId = this.generateActionId(eventId); + const factory = uiActions.getActionFactory(event.action.factoryId); + const actionDefinition: ActionDefinition = { + ...factory.create(action as SerializedAction), + id: actionId, + isCompatible, + getDisplayName: () => name, + getIconType: context => factory.getIconType(context), + }; + + uiActions.attachAction(triggerId as any, actionDefinition as any); + } + + protected killAction(actionId: string) {} } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts index 69f218171520c..a92909261da32 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SerializedAction } from './action_internal'; +import { SerializedAction } from './types'; /** * Serialized representation of event-action pair, used to persist in storage. @@ -25,7 +25,7 @@ import { SerializedAction } from './action_internal'; export interface SerializedEvent { eventId: string; triggerId: string; - action: SerializedAction; + action: SerializedAction; } /** diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index f2c105bca9d45..0ddba197aced6 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -24,3 +24,5 @@ export * from './action_factory'; export * from './create_action'; export * from './incompatible_action_error'; export * from './dynamic_action_storage'; +export * from './dynamic_action_manager'; +export * from './types'; diff --git a/src/plugins/ui_actions/public/actions/action_definition.ts b/src/plugins/ui_actions/public/actions/types.ts similarity index 62% rename from src/plugins/ui_actions/public/actions/action_definition.ts rename to src/plugins/ui_actions/public/actions/types.ts index b3456a09879a2..465f091e45ef1 100644 --- a/src/plugins/ui_actions/public/actions/action_definition.ts +++ b/src/plugins/ui_actions/public/actions/types.ts @@ -17,19 +17,8 @@ * under the License. */ -import { ActionType, ActionContextMapping } from '../types'; -import { Presentable } from '../util/presentable'; - -export interface ActionDefinition - extends Partial> { - /** - * ID of the action factory for this action. Action factories are registered - * int X-Pack `ui_actions` plugin. - */ - readonly type?: T; - - /** - * Executes the action. - */ - execute(context: ActionContextMapping[T]): Promise; +export interface SerializedAction { + readonly factoryId: string; + readonly name: string; + readonly config: Config; } diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index e27cc04a7d759..bb0a4c95f7e8f 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -44,4 +44,4 @@ export { } from './util'; export { Trigger, TriggerContext } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; -export { ActionByType } from './actions'; +export { ActionByType, DynamicActionManager as UiActionsDynamicActionManager } from './actions'; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index 65a18f0460f22..2d946a8d706ab 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -102,6 +102,8 @@ describe('UiActionsService', () => { type: 'test' as ActionType, }); }); + + test.todo('return action instance'); }); describe('.getTriggerActions()', () => { @@ -481,5 +483,7 @@ describe('UiActionsService', () => { test.todo('.getActionFactories() returns empty array if no action factories registered'); test.todo('can register an action factory'); test.todo('can retrieve all action factories'); + test.todo('can retrieve action factory by ID'); + test.todo('throws when retrieving action factory that does not exist'); }); }); diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 9dc463123e6af..e7724c3ac92ff 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -90,12 +90,18 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = (definition: A) => { + public readonly registerAction = ( + definition: A + ): ActionInternal => { if (this.actions.has(definition.id)) { throw new Error(`Action [action.id = ${definition.id}] already registered.`); } - this.actions.set(definition.id, new ActionInternal(definition)); + const action = new ActionInternal(definition); + + this.actions.set(action.id, action); + + return action; }; public readonly getAction = (id: string): ActionInternal => { @@ -237,6 +243,16 @@ export class UiActionsService { this.actionFactories.set(actionFactory.id, actionFactory); }; + public readonly getActionFactory = (actionFactoryId: string): AnyActionFactory => { + const actionFactory = this.actionFactories.get(actionFactoryId); + + if (!actionFactory) { + throw new Error(`Action factory [actionFactoryId = ${actionFactoryId}] does not exist.`); + } + + return actionFactory; + }; + /** * Returns an array of all action factories. */ diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 1b1e52f782aca..56b9dabaec001 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -98,7 +98,10 @@ export const dashboardDrilldownActionFactory: ActionFactoryDefinition< return Promise.resolve(true); }, order: 0, - create: () => null as any, + create: () => ({ + id: 'test', + execute: async () => alert('Navigate to dashboard!'), + }), }; export const dashboardFactory = new ActionFactory(dashboardDrilldownActionFactory); diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index 8f7ff48270d81..185ae218a6ffa 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -57,8 +57,14 @@ export class DrilldownService { }); }; - registerDrilldown({ ...dashboardDrilldownActionFactory, execute: () => {} } as any); - registerDrilldown({ ...urlDrilldownActionFactory, execute: () => {} } as any); + registerDrilldown({ + ...dashboardDrilldownActionFactory, + execute: () => alert('Dashboard drilldown!'), + } as any); + registerDrilldown({ + ...urlDrilldownActionFactory, + execute: () => alert('URL drilldown!'), + } as any); return { registerDrilldown, From ab6fb4b3babbfab4d50336d15c6c963004f030fb Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 11 Mar 2020 12:29:52 +0100 Subject: [PATCH 17/79] Drilldown events 5 (#59885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 display drilldowns in context menu only on one embed * feat: 🎸 clear dynamic actions from registry when embed unloads * fix: 🐛 fix OSS TypeScript errors --- .../public/lib/embeddables/embeddable.tsx | 24 +++++++++-- .../public/lib/embeddables/i_embeddable.ts | 8 ++++ .../public/actions/create_action.ts | 19 +++++--- .../public/actions/dynamic_action_manager.ts | 27 +++++++----- src/plugins/ui_actions/public/mocks.ts | 15 ++++--- .../public/service/ui_actions_service.ts | 43 +++++++++++++++++++ .../actions/flyout_edit_drilldown/i18n.ts | 14 ++++++ .../actions/flyout_edit_drilldown/index.tsx | 38 +++++----------- .../flyout_edit_drilldown/menu_item.tsx | 34 +++++++++++++++ .../dashboard_drilldowns_services.ts | 8 ++-- 10 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 499d9faa9b1d7..0bb78ff2fdc31 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -40,6 +40,10 @@ export abstract class Embeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput > implements IEmbeddable { + static runtimeId: number = 0; + + public readonly runtimeId = Embeddable.runtimeId++; + public readonly parent?: IContainer; public readonly isContainer: boolean = false; public abstract readonly type: string; @@ -63,7 +67,7 @@ export abstract class Embeddable< if (!this.params.uiActions) return undefined; if (!this.__dynamicActions) { this.__dynamicActions = new UiActionsDynamicActionManager({ - isCompatible: async () => true, + isCompatible: async ({ embeddable }: any) => embeddable.runtimeId === this.runtimeId, storage: new EmbeddableActionStorage(this), uiActions: this.params.uiActions, }); @@ -78,7 +82,6 @@ export abstract class Embeddable< parent?: IContainer, public readonly params: EmbeddableParams = {} ) { - window.emb = this; this.id = input.id; this.output = { title: getPanelTitle(input, output), @@ -104,7 +107,12 @@ export abstract class Embeddable< } if (this.dynamicActions) { - this.dynamicActions.start(); + this.dynamicActions.start().catch(error => { + /* eslint-disable */ + console.log('Failed to start embeddable dynamic actions', this); + console.error(error); + /* eslint-enable */ + }); } } @@ -184,6 +192,16 @@ export abstract class Embeddable< */ public destroy(): void { this.destoyed = true; + + if (this.dynamicActions) { + this.dynamicActions.stop().catch(error => { + /* eslint-disable */ + console.log('Failed to stop embeddable dynamic actions', this); + console.error(error); + /* eslint-enable */ + }); + } + if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 7b1e3076142bd..58fc7327232b8 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -83,6 +83,14 @@ export interface IEmbeddable< **/ readonly id: string; + /** + * Unique ID an embeddable is assigned each time it is initialized. This ID + * is different for different instances of the same embeddable. For example, + * if the same dashboard is rendered twice on the screen, all embeddable + * instances will have a unique `runtimeId`. + */ + readonly runtimeId?: number; + /** * Default implementation of dynamic action API for embeddables. */ diff --git a/src/plugins/ui_actions/public/actions/create_action.ts b/src/plugins/ui_actions/public/actions/create_action.ts index 5b10a8dc83a62..8f1cd23715d3f 100644 --- a/src/plugins/ui_actions/public/actions/create_action.ts +++ b/src/plugins/ui_actions/public/actions/create_action.ts @@ -17,14 +17,19 @@ * under the License. */ -import { Ensure } from '@kbn/utility-types'; -import { Action } from './action'; -import { TriggerContextMapping } from '../types'; +import { ActionContextMapping } from '../types'; +import { ActionByType } from './action'; +import { ActionType } from '../types'; import { ActionDefinition } from './action'; -export function createAction( - action: ActionDefinition> -): Action { +interface ActionDefinitionByType + extends Omit, 'id'> { + id?: string; +} + +export function createAction( + action: ActionDefinitionByType +): ActionByType { return { getIconType: () => undefined, order: 0, @@ -33,5 +38,5 @@ export function createAction( getDisplayName: () => '', getHref: () => undefined, ...action, - } as Action; + } as ActionByType; } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index d48d78e896c9a..489de32d12032 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -25,14 +25,17 @@ import { ActionDefinition } from './action'; export interface DynamicActionManagerParams { storage: ActionStorage; - uiActions: Pick; + uiActions: Pick< + UiActionsService, + 'addTriggerAction' | 'removeTriggerAction' | 'getActionFactory' + >; isCompatible: (context: C) => Promise; } export class DynamicActionManager { static idPrefixCounter = 0; - private readonly idPrefix = 'DYN_ACTION_' + DynamicActionManager.idPrefixCounter++; + private readonly idPrefix = `D_ACTION_${DynamicActionManager.idPrefixCounter++}_`; constructor(protected readonly params: DynamicActionManagerParams) {} @@ -49,15 +52,11 @@ export class DynamicActionManager { } public async stop() { - /* - const { storage, uiActions } = this.params; - const events = await storage.list(); + const events = await this.params.storage.list(); for (const event of events) { - uiActions.detachAction(event.triggerId, event.action.id); - uiActions.unregisterAction(event.action.id); + this.killAction(event); } - */ } public async createEvent(action: SerializedAction, triggerId = 'VALUE_CLICK_TRIGGER') { @@ -71,6 +70,10 @@ export class DynamicActionManager { this.reviveAction(event); } + public async count(): Promise { + return await this.params.storage.count(); + } + protected reviveAction(event: SerializedEvent) { const { eventId, triggerId, action } = event; const { uiActions, isCompatible } = this.params; @@ -86,8 +89,12 @@ export class DynamicActionManager { getIconType: context => factory.getIconType(context), }; - uiActions.attachAction(triggerId as any, actionDefinition as any); + uiActions.addTriggerAction(triggerId as any, actionDefinition); } - protected killAction(actionId: string) {} + protected killAction({ eventId, triggerId }: SerializedEvent) { + const { uiActions } = this.params; + const actionId = this.generateActionId(eventId); + uiActions.removeTriggerAction(triggerId as any, actionId); + } } diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 786ee4b5bd025..34b256e4a9efb 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -39,19 +39,22 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { + addTriggerAction: jest.fn(), attachAction: jest.fn(), - registerAction: jest.fn(), - registerTrigger: jest.fn(), - registerActionFactory: jest.fn(), - getAction: jest.fn(), + clear: jest.fn(), detachAction: jest.fn(), executeTriggerActions: jest.fn(), + fork: jest.fn(), + getAction: jest.fn(), getActionFactories: jest.fn(), + getActionFactory: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), - clear: jest.fn(), - fork: jest.fn(), + registerAction: jest.fn(), + registerActionFactory: jest.fn(), + registerTrigger: jest.fn(), + removeTriggerAction: jest.fn(), }; return startContract; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index e7724c3ac92ff..7f6c2a2d41572 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -35,6 +35,7 @@ import { AnyActionFactoryDefinition, ActionFactory, AnyActionFactory, + ActionDefinition, } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -104,6 +105,48 @@ export class UiActionsService { return action; }; + protected readonly unregisterAction = (actionId: string): void => { + if (!this.actions.has(actionId)) { + throw new Error(`Action [action.id = ${actionId}] is not registered.`); + } + + this.actions.delete(actionId); + }; + + public readonly addTriggerAction = ( + triggerId: TriggerId, + definition: ActionDefinition + ) => { + // Check if trigger exists, if not, next line throws. + this.getTrigger(triggerId); + + const action = this.registerAction(definition); + this.__attachAction(triggerId, action.id); + + return action; + }; + + public readonly removeTriggerAction = ( + triggerId: TriggerId, + actionId: string + ) => { + this.detachAction(triggerId, actionId); + this.unregisterAction(actionId); + }; + + // public readonly removeTriggerAction = + + protected readonly __attachAction = ( + triggerId: TriggerId, + actionId: string + ): void => { + const actionIds = this.triggerToActions.get(triggerId); + + if (!actionIds!.find(id => id === actionId)) { + this.triggerToActions.set(triggerId, [...actionIds!, actionId]); + } + }; + public readonly getAction = (id: string): ActionInternal => { if (!this.actions.has(id)) { throw new Error(`Action [action.id = ${id}] not registered.`); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts new file mode 100644 index 0000000000000..4e2e5eb7092e4 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/i18n.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtDisplayName = i18n.translate( + 'xpack.dashboard.panel.openFlyoutEditDrilldown.displayName', + { + defaultMessage: 'Manage drilldowns', + } +); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index 2fefcb50b7afc..251ee2bf906ee 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -5,34 +5,24 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; -import { EuiNotificationBadge } from '@elastic/eui'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { reactToUiComponent, toMountPoint, } from '../../../../../../../../src/plugins/kibana_react/public'; -import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; +import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; +import { txtDisplayName } from './i18n'; +import { MenuItem } from './menu_item'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; -export interface FlyoutEditDrilldownActionContext { - embeddable: IEmbeddable; -} - -const drilldownsData = [{}, {}]; - export interface FlyoutEditDrilldownParams { overlays: () => Promise; drilldowns: () => Promise; } -const displayName = i18n.translate('xpack.dashboard.panel.openFlyoutEditDrilldown.displayName', { - defaultMessage: 'Manage drilldowns', -}); - export class FlyoutEditDrilldownAction implements ActionByType { public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; @@ -41,31 +31,23 @@ export class FlyoutEditDrilldownAction implements ActionByType = () => { - return ( - <> - {displayName}{' '} - - {drilldownsData.length} - - - ); - }; + MenuItem = reactToUiComponent(MenuItem); - MenuItem = reactToUiComponent(this.ReactComp); + public async isCompatible({ embeddable }: EmbeddableContext) { + if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; + if (!embeddable.dynamicActions) return false; - public async isCompatible({ embeddable }: FlyoutEditDrilldownActionContext) { - return embeddable.getInput().viewMode === 'edit' && drilldownsData.length > 0; + return (await embeddable.dynamicActions.count()) > 0; } - public async execute(context: FlyoutEditDrilldownActionContext) { + public async execute(context: EmbeddableContext) { const overlays = await this.params.overlays(); const drilldowns = await this.params.drilldowns(); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx new file mode 100644 index 0000000000000..fd46dafd36b73 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiNotificationBadge } from '@elastic/eui'; +import useMountedState from 'react-use/lib/useMountedState'; +import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; +import { txtDisplayName } from './i18n'; + +export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) => { + const isMounted = useMountedState(); + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + if (!context.embeddable.dynamicActions) return; + context.embeddable.dynamicActions.count().then(result => { + if (!isMounted()) return; + setCount(result); + }); + }, [context.embeddable.dynamicActions, isMounted]); + + const badge = !count ? null : ( + {count} + ); + + return ( + <> + {txtDisplayName} {badge} + + ); +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 74be910255b17..6b0844aa9e20d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -6,12 +6,14 @@ import { CoreSetup } from 'src/core/public'; import { SetupDependencies } from '../../plugin'; -import { CONTEXT_MENU_DRILLDOWNS_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; +import { + CONTEXT_MENU_DRILLDOWNS_TRIGGER, + EmbeddableContext, +} from '../../../../../../src/plugins/embeddable/public'; import { FlyoutCreateDrilldownAction, FlyoutCreateDrilldownActionContext, FlyoutEditDrilldownAction, - FlyoutEditDrilldownActionContext, OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; @@ -20,7 +22,7 @@ import { DrilldownsStartContract } from '../../../../drilldowns/public'; declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { [OPEN_FLYOUT_ADD_DRILLDOWN]: FlyoutCreateDrilldownActionContext; - [OPEN_FLYOUT_EDIT_DRILLDOWN]: FlyoutEditDrilldownActionContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; } } From 0f3ff3ec31eaa9bf858de01ab0aa4b1102a79e2e Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 11 Mar 2020 15:22:57 +0100 Subject: [PATCH 18/79] basic integration of components with dynamicActionManager --- .../public/actions/dynamic_action_manager.ts | 29 ++++ src/plugins/ui_actions/public/index.ts | 2 + .../actions/flyout_create_drilldown/index.tsx | 1 + .../actions/flyout_edit_drilldown/index.tsx | 1 + ...nnected_flyout_manage_drilldowns.story.tsx | 3 +- ...onnected_flyout_manage_drilldowns.test.tsx | 13 +- .../connected_flyout_manage_drilldowns.tsx | 126 +++++++++++++++++- .../test_data.ts | 31 +++++ .../flyout_drilldown_wizard.tsx | 10 +- 9 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 489de32d12032..fbf1092de29af 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -70,6 +70,35 @@ export class DynamicActionManager { this.reviveAction(event); } + public async updateEvent( + eventId: string, + action: SerializedAction, + triggerId = 'VALUE_CLICK_TRIGGER' + ) { + const event: SerializedEvent = { + eventId, + triggerId, + action, + }; + + const oldEvent = await this.params.storage.read(eventId); + this.killAction(oldEvent); + await this.params.storage.update(event); + this.reviveAction(event); + } + + public async deleteEvents(eventIds: string[]) { + const eventsToKill = (await this.params.storage.list()).filter(event => + eventIds.includes(event.eventId) + ); + await Promise.all(eventIds.map(eventId => this.params.storage.remove(eventId))); + eventsToKill.forEach(event => this.killAction(event)); + } + + public async list(): Promise { + return await this.params.storage.list(); + } + public async count(): Promise { return await this.params.storage.count(); } diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index bb0a4c95f7e8f..cf3c377db148c 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,6 +35,8 @@ export { ActionStorage as UiActionsActionStorage, SerializedEvent as UiActionsSerializedEvent, SerializedAction as UiActionsSerializedAction, + AnyActionFactory, + DynamicActionManager, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; export { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index 94c4830fb8638..2fe010fdf9d9e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -54,6 +54,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} context={context} viewMode={'create'} + dynamicActionsManager={context.embeddable.dynamicActions!} /> ) ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index 251ee2bf906ee..9ff3ed7105866 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -57,6 +57,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} context={context} viewMode={'manage'} + dynamicActionsManager={context.embeddable.dynamicActions!} /> ) ); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index ae9dbfb359a7b..deb47fa093000 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -15,6 +15,7 @@ import { } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; +import { mockDynamicActionManager } from './test_data'; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { @@ -29,6 +30,6 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index b19a69104ee73..b11de5c681083 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -14,6 +14,7 @@ import { } from '../../../../advanced_ui_actions/public/components/action_wizard/test_data'; import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import { mockDynamicActionManager } from './test_data'; const storage = new Storage(new StubBrowserStorage()); const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ @@ -35,7 +36,9 @@ beforeEach(() => { }); test(' should render in manage view and should allow to create new drilldown', async () => { - const screen = render(); + const screen = render( + + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); @@ -71,7 +74,9 @@ test(' should render in manage view and should allow to }); test('Should show drilldown welcome message. Should be able to dismiss it', async () => { - let screen = render(); + let screen = render( + + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); @@ -81,7 +86,9 @@ test('Should show drilldown welcome message. Should be able to dismiss it', asyn expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); cleanup(); - screen = render(); + screen = render( + + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index f95c176c52155..110f70632ecc0 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -9,12 +9,20 @@ import { AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, } from '../../../../advanced_ui_actions/public'; -import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; +import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; +import { + AnyActionFactory, + DynamicActionManager, + UiActionsSerializedEvent, + UiActionsSerializedAction, +} from '../../../../../../src/plugins/ui_actions/public'; + interface ConnectedFlyoutManageDrilldownsProps { context: Context; + dynamicActionsManager: DynamicActionManager; viewMode?: 'create' | 'manage'; onClose?: () => void; } @@ -38,6 +46,10 @@ export function createFlyoutManageDrilldowns({ // This is ok to assume this is static, // because all action factories should be registered in setup phase const allActionFactories = advancedUiActions.actionFactory.getAll(); + const allActionFactoriesById = allActionFactories.reduce((acc, next) => { + acc[next.id] = next; + return acc; + }, {} as Record); return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; @@ -50,15 +62,36 @@ export function createFlyoutManageDrilldowns({ const [route, setRoute] = useState( () => (isCreateOnly ? Routes.Create : Routes.Manage) // initial state is different depending on `viewMode` ); + const [currentEditId, setCurrentEditId] = useState(null); const [shouldShowWelcomeMessage, onHideWelcomeMessage] = useWelcomeMessage(storage); + const { + drilldowns, + createDrilldown, + editDrilldown, + deleteDrilldown, + } = useDrilldownsStateManager(props.dynamicActionsManager); + /** * isCompatible promise is not yet resolved. * Skip rendering until it is resolved */ if (!actionFactories) return null; + function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined { + if (route !== Routes.Edit) return undefined; + if (!currentEditId) return undefined; + const drilldownToEdit = drilldowns.find(d => d.eventId === currentEditId); + if (!drilldownToEdit) return undefined; + + return { + actionFactory: allActionFactoriesById[drilldownToEdit.action.factoryId], + actionConfig: drilldownToEdit.action.config as object, // TODO: types + name: drilldownToEdit.action.name, + }; + } + switch (route) { case Routes.Create: case Routes.Edit: @@ -68,9 +101,24 @@ export function createFlyoutManageDrilldowns({ onWelcomeHideClick={onHideWelcomeMessage} drilldownActionFactories={actionFactories} onClose={props.onClose} - mode={Routes.Create ? 'create' : 'edit'} + mode={route === Routes.Create ? 'create' : 'edit'} onBack={isCreateOnly ? undefined : () => setRoute(Routes.Manage)} - onSubmit={() => { + onSubmit={({ actionConfig, actionFactory, name }) => { + if (route === Routes.Create) { + createDrilldown({ + name, + config: actionConfig, + factoryId: actionFactory.id, + }); + } else { + // edit + editDrilldown(currentEditId!, { + name, + config: actionConfig, + factoryId: actionFactory.id, + }); + } + if (isCreateOnly) { if (props.onClose) { props.onClose(); @@ -78,11 +126,16 @@ export function createFlyoutManageDrilldowns({ } else { setRoute(Routes.Manage); } + + setCurrentEditId(null); }} onDelete={() => { + deleteDrilldown(currentEditId!); setRoute(Routes.Manage); + setCurrentEditId(null); }} actionFactoryContext={props.context} + initialDrilldownWizardConfig={resolveInitialDrilldownWizardConfig()} /> ); @@ -92,12 +145,23 @@ export function createFlyoutManageDrilldowns({ {}} - onEdit={() => { + drilldowns={drilldowns.map(drilldown => ({ + id: drilldown.eventId, + name: drilldown.action.name, + actionTypeDisplayName: + allActionFactoriesById[drilldown.action.factoryId]?.getDisplayName(props.context) ?? + drilldown.action.factoryId, + }))} + onDelete={ids => { + setCurrentEditId(null); + deleteDrilldown(ids); + }} + onEdit={id => { + setCurrentEditId(id); setRoute(Routes.Edit); }} onCreate={() => { + setCurrentEditId(null); setRoute(Routes.Create); }} onClose={props.onClose} @@ -146,3 +210,53 @@ function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] { }, ]; } + +function useDrilldownsStateManager(actionManager: DynamicActionManager) { + const [isLoading, setIsLoading] = useState(false); + const [drilldowns, setDrilldowns] = useState([]); + + function reload() { + setIsLoading(true); + actionManager.list().then(res => { + setDrilldowns(res); + setIsLoading(false); + }); + } + + useEffect(() => { + reload(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { + setIsLoading(true); + actionManager.createEvent(action, triggerId).then(() => { + setIsLoading(false); + reload(); + }); + } + + function editDrilldown( + drilldownId: string, + action: UiActionsSerializedAction, + triggerId?: string + ) { + setIsLoading(true); + actionManager.updateEvent(drilldownId, action, triggerId).then(() => { + setIsLoading(false); + reload(); + }); + } + + function deleteDrilldown(drilldownIds: string | string[]) { + setIsLoading(true); + actionManager + .deleteEvents(Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]) + .then(() => { + setIsLoading(false); + reload(); + }); + } + + return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown }; +} diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts new file mode 100644 index 0000000000000..bca343f51d449 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + DynamicActionManager, + UiActionsSerializedAction, +} from '../../../../../../src/plugins/ui_actions/public'; + +class MockDynamicActionManager implements PublicMethodsOf { + async count() { + return 0; + } + async createEvent(action: UiActionsSerializedAction, triggerId?: string) {} + async deleteEvents(eventIds: string[]) {} + async list() { + return []; + } + async updateEvent( + eventId: string, + action: UiActionsSerializedAction, + triggerId?: string + ) {} + + async start() {} + async stop() {} +} + +export const mockDynamicActionManager = (new MockDynamicActionManager() as unknown) as DynamicActionManager; diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index de501aafe90c2..71cea97fcad69 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -35,7 +35,7 @@ export interface FlyoutDrilldownWizardProps< > { drilldownActionFactories: AnyActionFactory[]; - onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void; + onSubmit?: (drilldownWizardConfig: Required) => void; onDelete?: () => void; onClose?: () => void; onBack?: () => void; @@ -70,7 +70,9 @@ export function FlyoutDrilldownWizard< } ); - const isActionValid = (): boolean => { + const isActionValid = ( + config: DrilldownWizardConfig + ): config is Required => { if (!wizardConfig.name) return false; if (!wizardConfig.actionFactory) return false; if (!wizardConfig.actionConfig) return false; @@ -81,12 +83,12 @@ export function FlyoutDrilldownWizard< const footer = ( { - if (isActionValid()) { + if (isActionValid(wizardConfig)) { onSubmit(wizardConfig); } }} fill - isDisabled={!isActionValid()} + isDisabled={!isActionValid(wizardConfig)} > {mode === 'edit' ? txtEditDrilldownButtonLabel : txtCreateDrilldownButtonLabel} From f176550d5ecebfaf27e8439174ed225f375cb714 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 11 Mar 2020 19:19:56 +0100 Subject: [PATCH 19/79] =?UTF-8?q?fix:=20=F0=9F=90=9B=20don't=20overwrite?= =?UTF-8?q?=20explicitInput=20with=20combined=20input=20(#59938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../embeddable_action_storage.test.ts | 17 +++++++++++++++++ .../embeddables/embeddable_action_storage.ts | 3 --- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts index f67a41596868f..eada20721d3e0 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -58,6 +58,23 @@ describe('EmbeddableActionStorage', () => { expect(events2).toEqual([event]); }); + test('does not merge .getInput() into .updateInput()', async () => { + const embeddable = new TestEmbeddable(); + const storage = new EmbeddableActionStorage(embeddable); + const event: UiActionsSerializedEvent = { + eventId: 'EVENT_ID', + triggerId: 'TRIGGER-ID', + action: {} as any, + }; + + const spy = jest.spyOn(embeddable, 'updateInput'); + + await storage.create(event); + + expect(spy.mock.calls[0][0].id).toBe(undefined); + expect(spy.mock.calls[0][0].viewMode).toBe(undefined); + }); + test('can create multiple events', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts index b9a642fafeace..dc8466607a984 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -36,7 +36,6 @@ export class EmbeddableActionStorage implements UiActionsActionStorage { } this.embbeddable.updateInput({ - ...input, events: [...events, event], }); } @@ -55,7 +54,6 @@ export class EmbeddableActionStorage implements UiActionsActionStorage { } this.embbeddable.updateInput({ - ...input, events: [...events.slice(0, index), event, ...events.slice(index + 1)], }); } @@ -74,7 +72,6 @@ export class EmbeddableActionStorage implements UiActionsActionStorage { } this.embbeddable.updateInput({ - ...input, events: [...events.slice(0, index), ...events.slice(index + 1)], }); } From 4a4766eca149d1120c6d34a1a53e4598e2333404 Mon Sep 17 00:00:00 2001 From: Matt Kime Date: Wed, 11 Mar 2020 13:41:31 -0500 Subject: [PATCH 20/79] display drilldown count in embeddable edit mode --- .../embeddable/public/lib/embeddables/i_embeddable.ts | 2 ++ .../embeddable/public/lib/panel/embeddable_panel.tsx | 7 +++++++ .../public/lib/panel/panel_header/panel_header.tsx | 9 ++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 58fc7327232b8..abb98f55a1e48 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -22,6 +22,7 @@ import { UiActionsDynamicActionManager } from '../../../../../plugins/ui_actions import { Adapters } from '../types'; import { IContainer } from '../containers/i_container'; import { ViewMode } from '../types'; +import { EmbeddableActionStorage } from './embeddable_action_storage'; export interface EmbeddableInput { viewMode?: ViewMode; @@ -59,6 +60,7 @@ export interface IEmbeddable< I extends EmbeddableInput = EmbeddableInput, O extends EmbeddableOutput = EmbeddableOutput > { + actionStorage: EmbeddableActionStorage; /** * Is this embeddable an instance of a Container class, can it contain * nested embeddables? diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index d27dbc14a86d8..98ea706182ddf 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -74,6 +74,7 @@ interface State { hidePanelTitles: boolean; closeContextMenu: boolean; badges: Array>; + drilldownCount?: number; } export class EmbeddablePanel extends React.Component { @@ -194,6 +195,7 @@ export class EmbeddablePanel extends React.Component { badges={this.state.badges} embeddable={this.props.embeddable} headerId={headerId} + drilldownCount={this.state.drilldownCount} /> )}
@@ -205,6 +207,11 @@ export class EmbeddablePanel extends React.Component { if (this.embeddableRoot.current) { this.props.embeddable.render(this.embeddableRoot.current); } + this.props.embeddable.actionStorage.count().then(drilldownCount => { + if (this.mounted) { + this.setState({ drilldownCount }); + } + }); } closeMyContextMenuPanel = () => { diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 99516a1d21d6f..6a6a3510602d1 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -23,6 +23,7 @@ import { EuiIcon, EuiToolTip, EuiScreenReaderOnly, + EuiNotificationBadge, } from '@elastic/eui'; import classNames from 'classnames'; import React from 'react'; @@ -40,6 +41,7 @@ export interface PanelHeaderProps { badges: Array>; embeddable: IEmbeddable; headerId?: string; + drilldownCount?: number; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -90,6 +92,7 @@ export function PanelHeader({ badges, embeddable, headerId, + drilldownCount, }: PanelHeaderProps) { const viewDescription = getViewDescription(embeddable); const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== ''; @@ -147,7 +150,11 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - + {!isViewMode && drilldownCount && ( + + {drilldownCount} + + )} Date: Wed, 11 Mar 2020 14:08:09 -0500 Subject: [PATCH 21/79] display drilldown count in embeddable edit mode --- .../public/lib/embeddables/i_embeddable.ts | 2 -- .../public/lib/panel/embeddable_panel.tsx | 14 +++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index abb98f55a1e48..58fc7327232b8 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -22,7 +22,6 @@ import { UiActionsDynamicActionManager } from '../../../../../plugins/ui_actions import { Adapters } from '../types'; import { IContainer } from '../containers/i_container'; import { ViewMode } from '../types'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; export interface EmbeddableInput { viewMode?: ViewMode; @@ -60,7 +59,6 @@ export interface IEmbeddable< I extends EmbeddableInput = EmbeddableInput, O extends EmbeddableOutput = EmbeddableOutput > { - actionStorage: EmbeddableActionStorage; /** * Is this embeddable an instance of a Container class, can it contain * nested embeddables? diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 98ea706182ddf..980b17ff17f1c 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -207,11 +207,15 @@ export class EmbeddablePanel extends React.Component { if (this.embeddableRoot.current) { this.props.embeddable.render(this.embeddableRoot.current); } - this.props.embeddable.actionStorage.count().then(drilldownCount => { - if (this.mounted) { - this.setState({ drilldownCount }); - } - }); + + const dynamicActions = this.props.embeddable.dynamicActions; + if (dynamicActions) { + dynamicActions.count().then(drilldownCount => { + if (this.mounted) { + this.setState({ drilldownCount }); + } + }); + } } closeMyContextMenuPanel = () => { From 0d4bee2e80a753306cca767a8a42ffc8af1111eb Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 12 Mar 2020 11:38:34 +0100 Subject: [PATCH 22/79] improve wizard components. more tests. --- src/plugins/ui_actions/public/index.ts | 1 - .../action_wizard/action_wizard.tsx | 3 +- .../advanced_ui_actions/public/plugin.ts | 20 +-- .../actions/flyout_create_drilldown/index.tsx | 6 +- .../actions/flyout_edit_drilldown/index.tsx | 6 +- ...nnected_flyout_manage_drilldowns.story.tsx | 8 +- ...onnected_flyout_manage_drilldowns.test.tsx | 120 ++++++++++++++++-- .../connected_flyout_manage_drilldowns.tsx | 93 ++++++++------ .../test_data.ts | 37 +++++- .../drilldown_hello_bar.tsx | 4 +- .../flyout_drilldown_wizard.tsx | 10 +- .../flyout_list_manage_drilldowns.story.tsx | 9 +- .../flyout_list_manage_drilldowns.tsx | 2 - .../list_manage_drilldowns.story.tsx | 9 +- .../list_manage_drilldowns.test.tsx | 13 +- .../list_manage_drilldowns.tsx | 12 +- .../list_manage_drilldowns/test_data.ts | 11 -- .../public/services/drilldown_service.ts | 2 +- 18 files changed, 244 insertions(+), 122 deletions(-) delete mode 100644 x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index cf3c377db148c..fb2ba1f9f340d 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -35,7 +35,6 @@ export { ActionStorage as UiActionsActionStorage, SerializedEvent as UiActionsSerializedEvent, SerializedAction as UiActionsSerializedAction, - AnyActionFactory, DynamicActionManager, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 3d82d9483d1e9..06ab649ae0822 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -17,7 +17,6 @@ import { import { txtChangeButton } from './i18n'; import './action_wizard.scss'; import { AnyActionFactory } from '../../services'; -import { uiToReactComponent } from '../../../../../../src/plugins/kibana_react/public'; type ActionBaseConfig = object; type ActionFactoryBaseContext = object; @@ -144,7 +143,7 @@ const SelectedActionFactory: React.FC = ({
- {uiToReactComponent(actionFactory.CollectConfig)({ + {actionFactory.ReactCollectConfig({ config, onConfig: onConfigChange, })} diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 3d6aee460be1e..c0effa483cbfb 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -41,16 +41,10 @@ interface StartDependencies { uiActions: UiActionsStart; } -export interface SetupContract extends UiActionsSetup { - actionFactory: { - register: UiActionsSetup['registerActionFactory']; - }; -} -export interface StartContract extends UiActionsStart { - actionFactory: { - getAll: UiActionsStart['getActionFactories']; - }; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SetupContract extends UiActionsSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StartContract extends UiActionsStart {} declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -66,9 +60,6 @@ export class AdvancedUiActionsPublicPlugin public setup(core: CoreSetup, { uiActions }: SetupDependencies): SetupContract { return { ...uiActions, - actionFactory: { - register: uiActions.registerActionFactory, - }, }; } @@ -94,9 +85,6 @@ export class AdvancedUiActionsPublicPlugin return { ...uiActions, - actionFactory: { - getAll: uiActions.getActionFactories, - }, }; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index 2fe010fdf9d9e..a6be110c44a02 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -47,6 +47,10 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} context={context} viewMode={'create'} - dynamicActionsManager={context.embeddable.dynamicActions!} + dynamicActionManager={dynamicActionManager} /> ) ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index 9ff3ed7105866..6e94c8ac202ad 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -50,6 +50,10 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} context={context} viewMode={'manage'} - dynamicActionsManager={context.embeddable.dynamicActions!} + dynamicActionManager={dynamicActionManager} /> ) ); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index deb47fa093000..dbb9a4d63ef6e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -19,10 +19,8 @@ import { mockDynamicActionManager } from './test_data'; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { - actionFactory: { - getAll: () => { - return [dashboardFactory, urlFactory]; - }, + getActionFactories() { + return [dashboardFactory, urlFactory]; }, } as any, storage: new Storage(new StubBrowserStorage()), @@ -30,6 +28,6 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index b11de5c681083..cb52d5611810a 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -15,14 +15,14 @@ import { import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { mockDynamicActionManager } from './test_data'; +import { TEST_SUBJ_DRILLDOWN_ITEM } from '../list_manage_drilldowns'; +import { WELCOME_MESSAGE_TEST_SUBJ } from '../drilldown_hello_bar'; const storage = new Storage(new StubBrowserStorage()); const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { - actionFactory: { - getAll: () => { - return [dashboardFactory, urlFactory]; - }, + getActionFactories() { + return [dashboardFactory, urlFactory]; }, } as any, storage, @@ -35,14 +35,17 @@ beforeEach(() => { storage.clear(); }); -test(' should render in manage view and should allow to create new drilldown', async () => { +test('Allows to manage drilldowns', async () => { const screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + // no drilldowns in the list + expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0); + fireEvent.click(screen.getByText(/Create new/i)); let [createHeading, createButton] = screen.getAllByText(/Create Drilldown/i); @@ -52,8 +55,9 @@ test(' should render in manage view and should allow to expect(createButton).toBeDisabled(); // input drilldown name + const name = 'Test name'; fireEvent.change(screen.getByLabelText(/name/i), { - target: { value: 'Test' }, + target: { value: name }, }); // select URL one @@ -71,25 +75,115 @@ test(' should render in manage view and should allow to fireEvent.click(createButton); expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); + + await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(1)); + expect(screen.getByText(name)).toBeVisible(); + const editButton = screen.getByText(/edit/i); + fireEvent.click(editButton); + + expect(screen.getByText(/Edit Drilldown/i)).toBeVisible(); + // check that wizard is prefilled with current drilldown values + expect(screen.getByLabelText(/name/i)).toHaveValue(name); + expect(screen.getByLabelText(/url/i)).toHaveValue(URL); + + // input new drilldown name + const newName = 'New drilldown name'; + fireEvent.change(screen.getByLabelText(/name/i), { + target: { value: newName }, + }); + fireEvent.click(screen.getByText(/save/i)); + + expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); + await wait(() => screen.getByText(newName)); + + // delete drilldown from edit view + fireEvent.click(screen.getByText(/edit/i)); + fireEvent.click(screen.getByText(/delete/i)); + + expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible(); + await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0)); +}); + +test('Can delete multiple drilldowns', async () => { + const screen = render( + + ); + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + + const createDrilldown = async () => { + const oldCount = screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM).length; + fireEvent.click(screen.getByText(/Create new/i)); + fireEvent.change(screen.getByLabelText(/name/i), { + target: { value: 'test' }, + }); + fireEvent.click(screen.getByText(/Go to URL/i)); + fireEvent.change(screen.getByLabelText(/url/i), { + target: { value: 'https://elastic.co' }, + }); + fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); + await wait(() => + expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(oldCount + 1) + ); + }; + + await createDrilldown(); + await createDrilldown(); + await createDrilldown(); + + const checkboxes = screen.getAllByLabelText(/Select this drilldown/i); + expect(checkboxes).toHaveLength(3); + checkboxes.forEach(checkbox => fireEvent.click(checkbox)); + expect(screen.queryByText(/Create/i)).not.toBeInTheDocument(); + fireEvent.click(screen.getByText(/Delete \(3\)/i)); + + await wait(() => expect(screen.queryAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(0)); +}); + +test('Create only mode', async () => { + const onClose = jest.fn(); + const screen = render( + + ); + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getAllByText(/Create/i).length).toBeGreaterThan(0)); + fireEvent.change(screen.getByLabelText(/name/i), { + target: { value: 'test' }, + }); + fireEvent.click(screen.getByText(/Go to URL/i)); + fireEvent.change(screen.getByLabelText(/url/i), { + target: { value: 'https://elastic.co' }, + }); + fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); + + // TODO: fix act() warnings + // Need to wait for success in component before closing the dialog + expect(onClose).toBeCalled(); + expect(await mockDynamicActionManager.count()).toBe(1); }); test('Should show drilldown welcome message. Should be able to dismiss it', async () => { let screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - const welcomeMessageTestSubj = 'drilldowns-welcome-message-test-subj'; - expect(screen.getByTestId(welcomeMessageTestSubj)).toBeVisible(); + + expect(screen.getByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeVisible(); fireEvent.click(screen.getByText(/hide/i)); - expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); + expect(screen.queryByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeNull(); cleanup(); screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); - expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull(); + expect(screen.queryByTestId(WELCOME_MESSAGE_TEST_SUBJ)).toBeNull(); }); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 110f70632ecc0..b55c17eb527a0 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -5,24 +5,26 @@ */ import React, { useEffect, useState } from 'react'; +import useMount from 'react-use/lib/useMount'; +import useMountedState from 'react-use/lib/useMountedState'; import { AdvancedUiActionsActionFactory as ActionFactory, + AdvancedUiActionsAnyActionFactory as AnyActionFactory, AdvancedUiActionsStart, } from '../../../../advanced_ui_actions/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; - import { - AnyActionFactory, DynamicActionManager, UiActionsSerializedEvent, UiActionsSerializedAction, } from '../../../../../../src/plugins/ui_actions/public'; +import { DrilldownListItem } from '../list_manage_drilldowns'; interface ConnectedFlyoutManageDrilldownsProps { context: Context; - dynamicActionsManager: DynamicActionManager; + dynamicActionManager: DynamicActionManager; viewMode?: 'create' | 'manage'; onClose?: () => void; } @@ -43,9 +45,9 @@ export function createFlyoutManageDrilldowns({ advancedUiActions: AdvancedUiActionsStart; storage: IStorageWrapper; }) { - // This is ok to assume this is static, + // fine to assume this is static, // because all action factories should be registered in setup phase - const allActionFactories = advancedUiActions.actionFactory.getAll(); + const allActionFactories = advancedUiActions.getActionFactories(); const allActionFactoriesById = allActionFactories.reduce((acc, next) => { acc[next.id] = next; return acc; @@ -71,7 +73,7 @@ export function createFlyoutManageDrilldowns({ createDrilldown, editDrilldown, deleteDrilldown, - } = useDrilldownsStateManager(props.dynamicActionsManager); + } = useDrilldownsStateManager(props.dynamicActionManager); /** * isCompatible promise is not yet resolved. @@ -79,6 +81,9 @@ export function createFlyoutManageDrilldowns({ */ if (!actionFactories) return null; + /** + * Needed for edit mode to prefill wizard fields with data from current edited drilldown + */ function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined { if (route !== Routes.Edit) return undefined; if (!currentEditId) return undefined; @@ -87,11 +92,26 @@ export function createFlyoutManageDrilldowns({ return { actionFactory: allActionFactoriesById[drilldownToEdit.action.factoryId], - actionConfig: drilldownToEdit.action.config as object, // TODO: types + actionConfig: drilldownToEdit.action.config as object, // TODO: config is unknown, but we know it always extends object name: drilldownToEdit.action.name, }; } + /** + * Maps drilldown to list item view model + */ + function mapToDrilldownToDrilldownListItem( + drilldown: UiActionsSerializedEvent + ): DrilldownListItem { + return { + id: drilldown.eventId, + drilldownName: drilldown.action.name, + actionName: + allActionFactoriesById[drilldown.action.factoryId]?.getDisplayName(props.context) ?? + drilldown.action.factoryId, + }; + } + switch (route) { case Routes.Create: case Routes.Edit: @@ -145,13 +165,7 @@ export function createFlyoutManageDrilldowns({ ({ - id: drilldown.eventId, - name: drilldown.action.name, - actionTypeDisplayName: - allActionFactoriesById[drilldown.action.factoryId]?.getDisplayName(props.context) ?? - drilldown.action.factoryId, - }))} + drilldowns={drilldowns.map(mapToDrilldownToDrilldownListItem)} onDelete={ids => { setCurrentEditId(null); deleteDrilldown(ids); @@ -165,7 +179,6 @@ export function createFlyoutManageDrilldowns({ setRoute(Routes.Create); }} onClose={props.onClose} - context={props.context} /> ); } @@ -214,48 +227,48 @@ function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] { function useDrilldownsStateManager(actionManager: DynamicActionManager) { const [isLoading, setIsLoading] = useState(false); const [drilldowns, setDrilldowns] = useState([]); + const isMounted = useMountedState(); + + async function reload() { + if (!isMounted) { + // don't do any side effects anymore because component is already unmounted + return; + } - function reload() { setIsLoading(true); - actionManager.list().then(res => { - setDrilldowns(res); - setIsLoading(false); - }); + const drilldownsList = await actionManager.list(); + if (!isMounted) { + return; + } + + setDrilldowns(drilldownsList); + setIsLoading(false); } - useEffect(() => { + useMount(() => { reload(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }); - function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { + async function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { setIsLoading(true); - actionManager.createEvent(action, triggerId).then(() => { - setIsLoading(false); - reload(); - }); + await actionManager.createEvent(action, triggerId); + await reload(); } - function editDrilldown( + async function editDrilldown( drilldownId: string, action: UiActionsSerializedAction, triggerId?: string ) { setIsLoading(true); - actionManager.updateEvent(drilldownId, action, triggerId).then(() => { - setIsLoading(false); - reload(); - }); + await actionManager.updateEvent(drilldownId, action, triggerId); + await reload(); } - function deleteDrilldown(drilldownIds: string | string[]) { + async function deleteDrilldown(drilldownIds: string | string[]) { setIsLoading(true); - actionManager - .deleteEvents(Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]) - .then(() => { - setIsLoading(false); - reload(); - }); + await actionManager.deleteEvents(Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]); + await reload(); } return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown }; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index bca343f51d449..a4d09dddbaf31 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -4,25 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ +import uuid from 'uuid'; import { DynamicActionManager, UiActionsSerializedAction, + UiActionsSerializedEvent, } from '../../../../../../src/plugins/ui_actions/public'; class MockDynamicActionManager implements PublicMethodsOf { + private readonly events: UiActionsSerializedEvent[] = []; + async count() { - return 0; + return this.events.length; } - async createEvent(action: UiActionsSerializedAction, triggerId?: string) {} - async deleteEvents(eventIds: string[]) {} async list() { - return []; + return this.events; + } + async createEvent( + action: UiActionsSerializedAction, + triggerId: string = 'VALUE_CLICK_TRIGGER' + ) { + this.events.push({ + action, + triggerId, + eventId: uuid(), + }); + } + async deleteEvents(eventIds: string[]) { + eventIds.forEach(id => { + const idx = this.events.findIndex(e => e.eventId === id); + this.events.splice(idx, 1); + }); } async updateEvent( eventId: string, action: UiActionsSerializedAction, - triggerId?: string - ) {} + triggerId: string = 'VALUE_CLICK_TRIGGER' + ) { + const idx = this.events.findIndex(e => e.eventId === eventId); + this.events[idx] = { + eventId, + action, + triggerId, + }; + } async start() {} async stop() {} diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index 60ac90a954c27..6c975fe324744 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -22,6 +22,8 @@ export interface DrilldownHelloBarProps { onHideClick?: () => void; } +export const WELCOME_MESSAGE_TEST_SUBJ = 'drilldowns-welcome-message-test-subj'; + export const DrilldownHelloBar: React.FC = ({ docsLink, onHideClick = () => {}, @@ -29,7 +31,7 @@ export const DrilldownHelloBar: React.FC = ({ return ( diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index 71cea97fcad69..b7ed60084473c 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -22,17 +22,13 @@ import { AdvancedUiActionsAnyActionFactory as AnyActionFactory, } from '../../../../advanced_ui_actions/public'; -type ActionBaseConfig = object; - export interface DrilldownWizardConfig { name: string; actionFactory?: ActionFactory>; actionConfig?: ActionConfig; } -export interface FlyoutDrilldownWizardProps< - CurrentActionConfig extends ActionBaseConfig = ActionBaseConfig -> { +export interface FlyoutDrilldownWizardProps { drilldownActionFactories: AnyActionFactory[]; onSubmit?: (drilldownWizardConfig: Required) => void; @@ -49,9 +45,7 @@ export interface FlyoutDrilldownWizardProps< actionFactoryContext?: object; } -export function FlyoutDrilldownWizard< - CurrentActionConfig extends ActionBaseConfig = ActionBaseConfig ->({ +export function FlyoutDrilldownWizard({ onClose, onBack, onSubmit = () => {}, diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx index d3146bbc9d3ed..0529f0451b16a 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.story.tsx @@ -8,10 +8,15 @@ import * as React from 'react'; import { EuiFlyout } from '@elastic/eui'; import { storiesOf } from '@storybook/react'; import { FlyoutListManageDrilldowns } from './flyout_list_manage_drilldowns'; -import { drilldowns } from '../list_manage_drilldowns/test_data'; storiesOf('components/FlyoutListManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx index 0709608a642f3..a44a7ccccb4dc 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_list_manage_drilldowns/flyout_list_manage_drilldowns.tsx @@ -18,8 +18,6 @@ export interface FlyoutListManageDrilldownsProps { onDelete?: (drilldownIds: string[]) => void; showWelcomeMessage?: boolean; onWelcomeHideClick?: () => void; - - context?: object; // TODO DrilldownBaseContext? ActionBaseContext? } export function FlyoutListManageDrilldowns({ diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx index ae1b063449928..f700b2bdc3697 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx @@ -7,8 +7,13 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ListManageDrilldowns } from './list_manage_drilldowns'; -import { drilldowns } from './test_data'; storiesOf('components/ListManageDrilldowns', module).add('default', () => ( - + )); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx index 2d8a3ef8440cb..4a4d67b08b1d3 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.test.tsx @@ -7,13 +7,22 @@ import React from 'react'; import { cleanup, fireEvent, render } from '@testing-library/react/pure'; import '@testing-library/jest-dom/extend-expect'; // TODO: this should be global -import { drilldowns } from './test_data'; -import { ListManageDrilldowns, TEST_SUBJ_DRILLDOWN_ITEM } from './list_manage_drilldowns'; +import { + DrilldownListItem, + ListManageDrilldowns, + TEST_SUBJ_DRILLDOWN_ITEM, +} from './list_manage_drilldowns'; // TODO: for some reason global cleanup from RTL doesn't work // afterEach is not available for it globally during setup afterEach(cleanup); +const drilldowns: DrilldownListItem[] = [ + { id: '1', actionName: 'Dashboard', drilldownName: 'Drilldown 1' }, + { id: '2', actionName: 'Dashboard', drilldownName: 'Drilldown 2' }, + { id: '3', actionName: 'Dashboard', drilldownName: 'Drilldown 3' }, +]; + test('Render list of drilldowns', () => { const screen = render(); expect(screen.getAllByTestId(TEST_SUBJ_DRILLDOWN_ITEM)).toHaveLength(drilldowns.length); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index e3383596ea330..bc32a0d4c5dfa 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -19,11 +19,10 @@ import { txtSelectDrilldown, } from './i18n'; -// TODO: interface is temporary export interface DrilldownListItem { id: string; - actionTypeDisplayName: string; - name: string; + actionName: string; + drilldownName: string; } export interface ListManageDrilldownsProps { @@ -32,8 +31,6 @@ export interface ListManageDrilldownsProps { onEdit?: (id: string) => void; onCreate?: () => void; onDelete?: (ids: string[]) => void; - - context?: object; // TODO DrilldownBaseContext? ActionBaseContext? } const noop = () => {}; @@ -45,18 +42,17 @@ export function ListManageDrilldowns({ onEdit = noop, onCreate = noop, onDelete = noop, - context = {}, }: ListManageDrilldownsProps) { const [selectedDrilldowns, setSelectedDrilldowns] = useState([]); const columns: Array> = [ { - field: 'name', + field: 'actionName', name: 'Name', truncateText: true, }, { - field: 'actionTypeDisplayName', + field: 'drilldownName', name: 'Action', truncateText: true, }, diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts deleted file mode 100644 index 862c3efafa189..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/test_data.ts +++ /dev/null @@ -1,11 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export const drilldowns = [ - { id: '1', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 1' }, - { id: '2', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 2' }, - { id: '3', actionTypeDisplayName: 'Dashboard', name: 'Drilldown 3' }, -]; diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index 185ae218a6ffa..14207611a0440 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -41,7 +41,7 @@ export class DrilldownService { getIconType, execute, }) => { - advancedUiActions.actionFactory.register({ + advancedUiActions.registerActionFactory({ id, CollectConfig, createConfig, From ae98c6bc208e7ba3d097719cffbc49c6251c4cbf Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Thu, 12 Mar 2020 05:40:05 -0500 Subject: [PATCH 23/79] partial progress, dashboard drilldowns (#59977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * partial progress, dashboard drilldowns * partial progress, dashboard drilldowns * feat: 🎸 improve dashboard drilldown setup * feat: 🎸 wire in services into dashboard drilldown * chore: 🤖 add Storybook to dashboard_enhanced * feat: 🎸 create presentational * test: 💍 add stories * test: 💍 use presentation dashboar config component * feat: 🎸 wire in services into React component * docs: ✏️ add README to /components folder * feat: 🎸 increase importance of Dashboard drilldown * feat: 🎸 improve icon definition in drilldowns * chore: 🤖 remove unnecessary comment * chore: 🤖 add todos Co-authored-by: streamich --- src/dev/storybook/aliases.ts | 1 + x-pack/plugins/dashboard_enhanced/kibana.json | 2 +- .../public/components/README.md | 5 ++ .../dashboard_drilldown_config.story.tsx | 54 +++++++++++++++ .../dashboard_drilldown_config.test.tsx | 11 +++ .../dashboard_drilldown_config.tsx | 69 +++++++++++++++++++ .../dashboard_drilldown_config/i18n.ts | 14 ++++ .../dashboard_drilldown_config/index.ts | 7 ++ .../public/components/index.ts | 7 ++ .../dashboard_drilldowns_services.ts | 19 +++-- .../collect_config.test.tsx | 9 +++ .../collect_config.tsx | 55 +++++++++++++++ .../constants.ts | 7 ++ .../drilldown.test.tsx | 20 ++++++ .../drilldown.tsx | 59 ++++++++++++++++ .../dashboard_to_dashboard_drilldown/i18n.ts | 11 +++ .../dashboard_to_dashboard_drilldown/index.ts | 16 +++++ .../dashboard_to_dashboard_drilldown/types.ts | 19 +++++ .../dashboard_enhanced/scripts/storybook.js | 13 ++++ x-pack/plugins/drilldowns/public/index.ts | 2 + .../public/services/drilldown_service.ts | 14 ++-- x-pack/plugins/drilldowns/public/types.ts | 9 ++- 22 files changed, 410 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/README.md create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/components/index.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts create mode 100644 x-pack/plugins/dashboard_enhanced/scripts/storybook.js diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 52f618f611ca4..370abc120d475 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -22,6 +22,7 @@ export const storybookAliases = { apm: 'x-pack/legacy/plugins/apm/scripts/storybook.js', canvas: 'x-pack/legacy/plugins/canvas/scripts/storybook_new.js', codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts', + dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/scripts/storybook.js', drilldowns: 'x-pack/plugins/drilldowns/scripts/storybook.js', embeddable: 'src/plugins/embeddable/scripts/storybook.js', infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index e871bee3e4625..a9b6920ae369e 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "advancedUiActions", "drilldowns"] + "requiredPlugins": ["uiActions", "embeddable", "drilldowns"] } diff --git a/x-pack/plugins/dashboard_enhanced/public/components/README.md b/x-pack/plugins/dashboard_enhanced/public/components/README.md new file mode 100644 index 0000000000000..8081f8a2451cf --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/README.md @@ -0,0 +1,5 @@ +# Presentation React components + +Here we keep reusable *presentation* (aka *dumb*) React components—these +components should not be connected to state and ideally should not know anything +about Kibana. diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx new file mode 100644 index 0000000000000..8e204b044a136 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable no-console */ + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { DashboardDrilldownConfig } from '.'; + +export const dashboards = [ + { id: 'dashboard1', title: 'Dashboard 1' }, + { id: 'dashboard2', title: 'Dashboard 2' }, + { id: 'dashboard3', title: 'Dashboard 3' }, +]; + +const InteractiveDemo: React.FC = () => { + const [activeDashboardId, setActiveDashboardId] = React.useState('dashboard1'); + const [currentFilters, setCurrentFilters] = React.useState(false); + const [keepRange, setKeepRange] = React.useState(false); + + return ( + setActiveDashboardId(id)} + onCurrentFiltersToggle={() => setCurrentFilters(old => !old)} + onKeepRangeToggle={() => setKeepRange(old => !old)} + /> + ); +}; + +storiesOf('components/DashboardDrilldownConfig', module) + .add('default', () => ( + console.log('onDashboardSelect', e)} + /> + )) + .add('with switches', () => ( + console.log('onDashboardSelect', e)} + onCurrentFiltersToggle={() => console.log('onCurrentFiltersToggle')} + onKeepRangeToggle={() => console.log('onKeepRangeToggle')} + /> + )) + .add('interactive demo', () => ); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx new file mode 100644 index 0000000000000..911ff6f632635 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +test.todo('renders list of dashboards'); +test.todo('renders correct selected dashboard'); +test.todo('can change dashboard'); +test.todo('can toggle "use current filters" switch'); +test.todo('can toggle "date range" switch'); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx new file mode 100644 index 0000000000000..b45ba602b9bb1 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; +import { txtChooseDestinationDashboard } from './i18n'; + +export interface DashboardItem { + id: string; + title: string; +} + +export interface DashboardDrilldownConfigProps { + activeDashboardId?: string; + dashboards: DashboardItem[]; + currentFilters?: boolean; + keepRange?: boolean; + onDashboardSelect: (dashboardId: string) => void; + onCurrentFiltersToggle?: () => void; + onKeepRangeToggle?: () => void; +} + +export const DashboardDrilldownConfig: React.FC = ({ + activeDashboardId, + dashboards, + currentFilters, + keepRange, + onDashboardSelect, + onCurrentFiltersToggle, + onKeepRangeToggle, +}) => { + // TODO: use i18n below. + return ( + <> + + ({ value: id, text: title }))} + value={activeDashboardId} + onChange={e => onDashboardSelect(e.target.value)} + /> + + {!!onCurrentFiltersToggle && ( + + + + )} + {!!onKeepRangeToggle && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts new file mode 100644 index 0000000000000..38fe6dd150853 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtChooseDestinationDashboard = i18n.translate( + 'xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard', + { + defaultMessage: 'Choose destination dashboard', + } +); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts new file mode 100644 index 0000000000000..b9a64a3cc17e6 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dashboard_drilldown_config'; diff --git a/x-pack/plugins/dashboard_enhanced/public/components/index.ts b/x-pack/plugins/dashboard_enhanced/public/components/index.ts new file mode 100644 index 0000000000000..b9a64a3cc17e6 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/components/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dashboard_drilldown_config'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 6b0844aa9e20d..ed0cb425ee106 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -18,6 +18,7 @@ import { OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; import { DrilldownsStartContract } from '../../../../drilldowns/public'; +import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown'; declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -27,19 +28,25 @@ declare module '../../../../../../src/plugins/ui_actions/public' { } export class DashboardDrilldownsService { - bootstrap( + async bootstrap( core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, - { uiActions }: Pick + plugins: SetupDependencies ) { const overlays = async () => (await core.getStartServices())[0].overlays; const drilldowns = async () => (await core.getStartServices())[1].drilldowns; + const savedObjects = async () => (await core.getStartServices())[0].savedObjects.client; const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); - uiActions.registerAction(actionFlyoutCreateDrilldown); - uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); + plugins.uiActions.registerAction(actionFlyoutCreateDrilldown); + plugins.uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); - uiActions.registerAction(actionFlyoutEditDrilldown); - uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); + plugins.uiActions.registerAction(actionFlyoutEditDrilldown); + plugins.uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); + + const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ + savedObjects, + }); + plugins.drilldowns.registerDrilldown(dashboardToDashboardDrilldown); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx new file mode 100644 index 0000000000000..95101605ce468 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +test.todo('displays all dashboard in a list'); +test.todo('does not display dashboard on which drilldown is being created'); +test.todo('updates config object correctly'); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx new file mode 100644 index 0000000000000..778a6b3ef6b31 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useEffect } from 'react'; +import { CollectConfigProps } from './types'; +import { DashboardDrilldownConfig } from '../../../components/dashboard_drilldown_config'; +import { Params } from './drilldown'; + +export interface CollectConfigContainerProps extends CollectConfigProps { + params: Params; +} + +export const CollectConfigContainer: React.FC = ({ + config, + onConfig, + params: { savedObjects }, +}) => { + const [dashboards] = useState([ + { id: 'dashboard1', title: 'Dashboard 1' }, + { id: 'dashboard2', title: 'Dashboard 2' }, + { id: 'dashboard3', title: 'Dashboard 3' }, + { id: 'dashboard4', title: 'Dashboard 4' }, + ]); + + useEffect(() => { + // TODO: Load dashboards... + }, [savedObjects]); + + return ( + { + onConfig({ ...config, dashboardId }); + }} + onCurrentFiltersToggle={() => + onConfig({ + ...config, + useCurrentDashboardFilters: !config.useCurrentDashboardFilters, + }) + } + onKeepRangeToggle={() => + onConfig({ + ...config, + useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, + }) + } + /> + ); +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts new file mode 100644 index 0000000000000..e2a530b156da5 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/constants.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const DASHBOARD_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx new file mode 100644 index 0000000000000..0fb60bb1064a1 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.test.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +describe('.isConfigValid()', () => { + test.todo('returns false for incorrect config'); + test.todo('returns true for incorrect config'); +}); + +describe('.execute()', () => { + test.todo('navigates to correct dashboard'); + test.todo( + 'when user chooses to keep current filters, current fileters are set on destination dashboard' + ); + test.todo( + 'when user chooses to keep current time range, current time range is set on destination dashboard' + ); +}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx new file mode 100644 index 0000000000000..c839ef8ee04ef --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { CoreStart } from 'src/core/public'; +import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; +import { FactoryContext, ActionContext, Config, CollectConfigProps } from './types'; +import { CollectConfigContainer } from './collect_config'; +import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; +import { DrilldownsDrilldown as Drilldown } from '../../../../../drilldowns/public'; +import { txtGoToDashboard } from './i18n'; + +export const dashboards = [ + { id: 'dashboard1', title: 'Dashboard 1' }, + { id: 'dashboard2', title: 'Dashboard 2' }, +]; + +export interface Params { + savedObjects: () => Promise; +} + +export class DashboardToDashboardDrilldown + implements Drilldown { + constructor(protected readonly params: Params) {} + + // TODO: public readonly places = ['dashboard']; + + public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; + + public readonly order = 100; + + public readonly getDisplayName = () => txtGoToDashboard; + + public readonly euiIcon = 'dashboardApp'; + + private readonly ReactCollectConfig: React.FC = props => ( + + ); + + public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); + + public readonly createConfig = () => ({ + dashboardId: '123', + useCurrentDashboardDataRange: true, + useCurrentDashboardFilters: true, + }); + + public readonly isConfigValid = (config: Config) => { + if (!config.dashboardId) return false; + return true; + }; + + public readonly execute = () => { + alert('Go to another dashboard!'); + }; +} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts new file mode 100644 index 0000000000000..98b746bafd24a --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/i18n.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtGoToDashboard = i18n.translate('xpack.dashboard.drilldown.goToDashboard', { + defaultMessage: 'Go to Dashboard', +}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts new file mode 100644 index 0000000000000..58cf5cbad346b --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; +export { + DashboardToDashboardDrilldown, + Params as DashboardToDashboardDrilldownParams, +} from './drilldown'; +export { + FactoryContext as DashboardToDashboardFactoryContext, + ActionContext as DashboardToDashboardActionContext, + Config as DashboardToDashboardConfig, +} from './types'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts new file mode 100644 index 0000000000000..92f5a9be648bc --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EmbeddableVisTriggerContext } from '../../../../../../../src/plugins/embeddable/public'; +import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; + +export type FactoryContext = any; +export type ActionContext = EmbeddableVisTriggerContext; + +export interface Config { + dashboardId?: string; + useCurrentDashboardFilters: boolean; + useCurrentDashboardDataRange: boolean; +} + +export type CollectConfigProps = UiActionsCollectConfigProps; diff --git a/x-pack/plugins/dashboard_enhanced/scripts/storybook.js b/x-pack/plugins/dashboard_enhanced/scripts/storybook.js new file mode 100644 index 0000000000000..f2cbe4135f4cb --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/scripts/storybook.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'dashboard_enhanced', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], +}); diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 48eeb142b65a2..6da0b96ad631c 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -16,3 +16,5 @@ export { export function plugin() { return new DrilldownsPlugin(); } + +export { Drilldown as DrilldownsDrilldown } from './types'; diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index 14207611a0440..e258319a16b70 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -6,11 +6,11 @@ import { CoreSetup } from 'src/core/public'; import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; -import { Drilldown } from '../types'; +import { AnyDrilldown } from '../types'; // TODO: MOCK DATA import { - dashboardDrilldownActionFactory, + // dashboardDrilldownActionFactory, urlDrilldownActionFactory, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../advanced_ui_actions/public/components/action_wizard/test_data'; @@ -23,7 +23,7 @@ export interface DrilldownServiceSetupContract { /** * Convenience method to register a drilldown. */ - registerDrilldown: (drilldown: Drilldown) => void; + registerDrilldown: (drilldown: AnyDrilldown) => void; } export class DrilldownService { @@ -38,7 +38,7 @@ export class DrilldownService { createConfig, isConfigValid, getDisplayName, - getIconType, + euiIcon, execute, }) => { advancedUiActions.registerActionFactory({ @@ -47,22 +47,26 @@ export class DrilldownService { createConfig, isConfigValid, getDisplayName, - getIconType, + getIconType: () => euiIcon, isCompatible: async ({ place }: any) => (!places ? true : places.indexOf(place) > -1), create: config => ({ id: '', type: id as any, + getIconType: () => euiIcon, execute: async context => await execute(config, context), }), }); }; + /* registerDrilldown({ ...dashboardDrilldownActionFactory, execute: () => alert('Dashboard drilldown!'), } as any); + */ registerDrilldown({ ...urlDrilldownActionFactory, + euiIcon: 'link', execute: () => alert('URL drilldown!'), } as any); diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 7904a1969d2dc..4c5cfa2e596aa 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -13,7 +13,7 @@ export interface Drilldown< > extends Pick< ActionFactoryDefinition, - 'id' | 'createConfig' | 'CollectConfig' | 'isConfigValid' | 'getIconType' | 'getDisplayName' + 'id' | 'createConfig' | 'CollectConfig' | 'isConfigValid' | 'getDisplayName' > { /** * List of places where this drilldown should be available, e.g "dashboard". @@ -21,6 +21,11 @@ export interface Drilldown< */ places?: string[]; + /** + * Name of EUI icon to display next to this drilldown. + */ + euiIcon?: string; + /** * Implements the "navigation" action when user clicks something in the UI and * instance of this drilldown is triggered. @@ -31,3 +36,5 @@ export interface Drilldown< */ execute(config: Config, context: ExecutionContext): void; } + +export type AnyDrilldown = Drilldown; From 3200151ef6554573d15c58f4312c9ad9260bb761 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 12 Mar 2020 15:21:32 +0100 Subject: [PATCH 24/79] Manage drilldowns toasts. Add basic error handling. --- ...nnected_flyout_manage_drilldowns.story.tsx | 10 ++ ...onnected_flyout_manage_drilldowns.test.tsx | 47 +++++++- .../connected_flyout_manage_drilldowns.tsx | 103 ++++++++++++++---- .../i18n.ts | 88 +++++++++++++++ .../flyout_drilldown_wizard/i18n.ts | 10 +- x-pack/plugins/drilldowns/public/plugin.ts | 1 + 6 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index dbb9a4d63ef6e..5b1864a379fe4 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -24,6 +24,16 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ }, } as any, storage: new Storage(new StubBrowserStorage()), + notifications: { + toasts: { + addError: (...args: any[]) => { + alert(JSON.stringify(args)); + }, + addSuccess: (...args: any[]) => { + alert(JSON.stringify(args)); + }, + } as any, + }, }); storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index cb52d5611810a..7d6b7559ec936 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -17,8 +17,12 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { mockDynamicActionManager } from './test_data'; import { TEST_SUBJ_DRILLDOWN_ITEM } from '../list_manage_drilldowns'; import { WELCOME_MESSAGE_TEST_SUBJ } from '../drilldown_hello_bar'; +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { NotificationsStart } from 'kibana/public'; +import { toastDrilldownsCRUDError, toastDrilldownsFetchError } from './i18n'; const storage = new Storage(new StubBrowserStorage()); +const notifications = coreMock.createStart().notifications; const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ advancedUiActions: { getActionFactories() { @@ -26,6 +30,7 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ }, } as any, storage, + notifications, }); // https://github.com/elastic/kibana/issues/59469 @@ -33,6 +38,8 @@ afterEach(cleanup); beforeEach(() => { storage.clear(); + (notifications.toasts as jest.Mocked).addSuccess.mockClear(); + (notifications.toasts as jest.Mocked).addError.mockClear(); }); test('Allows to manage drilldowns', async () => { @@ -161,12 +168,48 @@ test('Create only mode', async () => { }); fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); - // TODO: fix act() warnings - // Need to wait for success in component before closing the dialog + await wait(() => expect(notifications.toasts.addSuccess).toBeCalled()); expect(onClose).toBeCalled(); expect(await mockDynamicActionManager.count()).toBe(1); }); +test("Error when can't fetch drilldown list", async () => { + const error = new Error('Oops'); + jest.spyOn(mockDynamicActionManager, 'list').mockImplementationOnce(async () => { + throw error; + }); + render(); + await wait(() => + expect(notifications.toasts.addError).toBeCalledWith(error, { + title: toastDrilldownsFetchError, + }) + ); +}); + +test("Error when can't save drilldown changes", async () => { + const error = new Error('Oops'); + jest.spyOn(mockDynamicActionManager, 'createEvent').mockImplementationOnce(async () => { + throw error; + }); + const screen = render( + + ); + // wait for initial render. It is async because resolving compatible action factories is async + await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); + fireEvent.click(screen.getByText(/Create new/i)); + fireEvent.change(screen.getByLabelText(/name/i), { + target: { value: 'test' }, + }); + fireEvent.click(screen.getByText(/Go to URL/i)); + fireEvent.change(screen.getByLabelText(/url/i), { + target: { value: 'https://elastic.co' }, + }); + fireEvent.click(screen.getAllByText(/Create Drilldown/i)[1]); + await wait(() => + expect(notifications.toasts.addError).toBeCalledWith(error, { title: toastDrilldownsCRUDError }) + ); +}); + test('Should show drilldown welcome message. Should be able to dismiss it', async () => { let screen = render( diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index b55c17eb527a0..2f6f921cab18a 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -12,6 +12,7 @@ import { AdvancedUiActionsAnyActionFactory as AnyActionFactory, AdvancedUiActionsStart, } from '../../../../advanced_ui_actions/public'; +import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; @@ -21,6 +22,14 @@ import { UiActionsSerializedAction, } from '../../../../../../src/plugins/ui_actions/public'; import { DrilldownListItem } from '../list_manage_drilldowns'; +import { + toastDrilldownCreated, + toastDrilldownDeleted, + toastDrilldownEdited, + toastDrilldownsCRUDError, + toastDrilldownsDeleted, + toastDrilldownsFetchError, +} from './i18n'; interface ConnectedFlyoutManageDrilldownsProps { context: Context; @@ -41,9 +50,11 @@ enum Routes { export function createFlyoutManageDrilldowns({ advancedUiActions, storage, + notifications, }: { advancedUiActions: AdvancedUiActionsStart; storage: IStorageWrapper; + notifications: NotificationsStart; }) { // fine to assume this is static, // because all action factories should be registered in setup phase @@ -73,13 +84,18 @@ export function createFlyoutManageDrilldowns({ createDrilldown, editDrilldown, deleteDrilldown, - } = useDrilldownsStateManager(props.dynamicActionManager); + } = useDrilldownsStateManager(props.dynamicActionManager, notifications); /** * isCompatible promise is not yet resolved. * Skip rendering until it is resolved */ if (!actionFactories) return null; + /** + * Drilldowns are not fetched yet or error happened during fetching + * In case of error user is notified with toast + */ + if (!drilldowns) return null; /** * Needed for edit mode to prefill wizard fields with data from current edited drilldown @@ -87,7 +103,7 @@ export function createFlyoutManageDrilldowns({ function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined { if (route !== Routes.Edit) return undefined; if (!currentEditId) return undefined; - const drilldownToEdit = drilldowns.find(d => d.eventId === currentEditId); + const drilldownToEdit = drilldowns?.find(d => d.eventId === currentEditId); if (!drilldownToEdit) return undefined; return { @@ -224,25 +240,50 @@ function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] { ]; } -function useDrilldownsStateManager(actionManager: DynamicActionManager) { +function useDrilldownsStateManager( + actionManager: DynamicActionManager, + notifications: NotificationsStart +) { const [isLoading, setIsLoading] = useState(false); - const [drilldowns, setDrilldowns] = useState([]); + const [drilldowns, setDrilldowns] = useState(); const isMounted = useMountedState(); - async function reload() { - if (!isMounted) { - // don't do any side effects anymore because component is already unmounted + async function run(op: () => Promise) { + setIsLoading(true); + try { + await op(); + } catch (e) { + notifications.toasts.addError(e, { + title: toastDrilldownsCRUDError, + }); + if (!isMounted) return; + setIsLoading(false); return; } - setIsLoading(true); - const drilldownsList = await actionManager.list(); + await reload(); + } + + async function reload() { if (!isMounted) { + // don't do any side effects anymore because component is already unmounted return; } - - setDrilldowns(drilldownsList); - setIsLoading(false); + if (!isLoading) { + setIsLoading(true); + } + try { + const drilldownsList = await actionManager.list(); + if (!isMounted) { + return; + } + setDrilldowns(drilldownsList); + setIsLoading(false); + } catch (e) { + notifications.toasts.addError(e, { + title: toastDrilldownsFetchError, + }); + } } useMount(() => { @@ -250,9 +291,13 @@ function useDrilldownsStateManager(actionManager: DynamicActionManager) { }); async function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { - setIsLoading(true); - await actionManager.createEvent(action, triggerId); - await reload(); + await run(async () => { + await actionManager.createEvent(action, triggerId); + notifications.toasts.addSuccess({ + title: toastDrilldownCreated.title, + text: toastDrilldownCreated.text(action.name), + }); + }); } async function editDrilldown( @@ -260,15 +305,31 @@ function useDrilldownsStateManager(actionManager: DynamicActionManager) { action: UiActionsSerializedAction, triggerId?: string ) { - setIsLoading(true); - await actionManager.updateEvent(drilldownId, action, triggerId); - await reload(); + await run(async () => { + await actionManager.updateEvent(drilldownId, action, triggerId); + notifications.toasts.addSuccess({ + title: toastDrilldownEdited.title, + text: toastDrilldownEdited.text(action.name), + }); + }); } async function deleteDrilldown(drilldownIds: string | string[]) { - setIsLoading(true); - await actionManager.deleteEvents(Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]); - await reload(); + await run(async () => { + drilldownIds = Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds]; + await actionManager.deleteEvents(drilldownIds); + notifications.toasts.addSuccess( + drilldownIds.length === 1 + ? { + title: toastDrilldownDeleted.title, + text: toastDrilldownDeleted.text, + } + : { + title: toastDrilldownsDeleted.title, + text: toastDrilldownsDeleted.text(drilldownIds.length), + } + ); + }); } return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown }; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts new file mode 100644 index 0000000000000..70f4d735e2a74 --- /dev/null +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/i18n.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const toastDrilldownCreated = { + title: i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedTitle', + { + defaultMessage: 'Drilldown created', + } + ), + text: (drilldownName: string) => + i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedText', { + defaultMessage: 'You created "{drilldownName}"', + values: { + drilldownName, + }, + }), +}; + +export const toastDrilldownEdited = { + title: i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedTitle', + { + defaultMessage: 'Drilldown edited', + } + ), + text: (drilldownName: string) => + i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedText', { + defaultMessage: 'You edited "{drilldownName}"', + values: { + drilldownName, + }, + }), +}; + +export const toastDrilldownDeleted = { + title: i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedTitle', + { + defaultMessage: 'Drilldown deleted', + } + ), + text: i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedText', + { + defaultMessage: 'You deleted a drilldown', + } + ), +}; + +export const toastDrilldownsDeleted = { + title: i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedTitle', + { + defaultMessage: 'Drilldowns deleted', + } + ), + text: (n: number) => + i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsDeletedText', + { + defaultMessage: 'You deleted {n} drilldowns', + values: { + n, + }, + } + ), +}; + +export const toastDrilldownsCRUDError = i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsCRUDErrorTitle', + { + defaultMessage: 'Error saving drilldown', + description: 'Title for generic error toast when persisting drilldown updates failed', + } +); + +export const toastDrilldownsFetchError = i18n.translate( + 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownsFetchErrorTitle', + { + defaultMessage: 'Error fetching drilldowns', + } +); diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts index 44f4a9c041a3b..a4a2754a444ab 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/i18n.ts @@ -7,35 +7,35 @@ import { i18n } from '@kbn/i18n'; export const txtCreateDrilldownTitle = i18n.translate( - 'xpack.drilldowns.components.FlyoutDrilldownWizard.CreateDrilldownTitle', + 'xpack.drilldowns.components.flyoutDrilldownWizard.createDrilldownTitle', { defaultMessage: 'Create Drilldown', } ); export const txtEditDrilldownTitle = i18n.translate( - 'xpack.drilldowns.components.FlyoutDrilldownWizard.EditDrilldownTitle', + 'xpack.drilldowns.components.flyoutDrilldownWizard.editDrilldownTitle', { defaultMessage: 'Edit Drilldown', } ); export const txtCreateDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.FlyoutDrilldownWizard.CreateDrilldownButtonLabel', + 'xpack.drilldowns.components.flyoutDrilldownWizard.createDrilldownButtonLabel', { defaultMessage: 'Create drilldown', } ); export const txtEditDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.FlyoutDrilldownWizard.EditDrilldownButtonLabel', + 'xpack.drilldowns.components.flyoutDrilldownWizard.editDrilldownButtonLabel', { defaultMessage: 'Save', } ); export const txtDeleteDrilldownButtonLabel = i18n.translate( - 'xpack.drilldowns.components.FlyoutDrilldownWizard.DeleteDrilldownButtonLabel', + 'xpack.drilldowns.components.flyoutDrilldownWizard.deleteDrilldownButtonLabel', { defaultMessage: 'Delete drilldown', } diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index ee6f591c1325b..bbc06847d5842 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -43,6 +43,7 @@ export class DrilldownsPlugin FlyoutManageDrilldowns: createFlyoutManageDrilldowns({ advancedUiActions: plugins.advancedUiActions, storage: new Storage(localStorage), + notifications: core.notifications, }), }; } From ff4e9ad8b6fe061d630a4340ab6046b7a1dd1bd6 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 12 Mar 2020 15:39:02 +0100 Subject: [PATCH 25/79] support order in action factory selector --- .../action_wizard/action_wizard.tsx | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 06ab649ae0822..481c3a3ef0194 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -173,19 +173,22 @@ const ActionFactorySelector: React.FC = ({ return ( - {actionFactories.map(actionFactory => ( - onActionFactorySelected(actionFactory)} - > - {actionFactory.getIconType(context) && ( - - )} - - ))} + {[...actionFactories] + .sort((f1, f2) => f1.order - f2.order) + .map(actionFactory => ( + onActionFactorySelected(actionFactory)} + > + {actionFactory.getIconType(context) && ( + + )} + {actionFactory.order} + + ))} ); }; From c7d4aa69b2cd0b9c4fcadc3b5f8da75ecbdbc340 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 12 Mar 2020 15:39:24 +0100 Subject: [PATCH 26/79] fix column order in manage drilldowns list --- .../list_manage_drilldowns/list_manage_drilldowns.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index bc32a0d4c5dfa..c27ff7bd5b6bf 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -47,12 +47,12 @@ export function ListManageDrilldowns({ const columns: Array> = [ { - field: 'actionName', + field: 'drilldownName', name: 'Name', truncateText: true, }, { - field: 'drilldownName', + field: 'actionName', name: 'Action', truncateText: true, }, From c731afa4a47d52d0a40930f378160e252c0466da Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 12 Mar 2020 16:52:58 +0100 Subject: [PATCH 27/79] remove accidental debug info --- .../public/components/action_wizard/action_wizard.tsx | 1 - .../connected_flyout_manage_drilldowns.tsx | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 481c3a3ef0194..5d605ca1c3b7f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -186,7 +186,6 @@ const ActionFactorySelector: React.FC = ({ {actionFactory.getIconType(context) && ( )} - {actionFactory.order} ))} diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 2f6f921cab18a..aad31191ca735 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -8,7 +8,6 @@ import React, { useEffect, useState } from 'react'; import useMount from 'react-use/lib/useMount'; import useMountedState from 'react-use/lib/useMountedState'; import { - AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsAnyActionFactory as AnyActionFactory, AdvancedUiActionsStart, } from '../../../../advanced_ui_actions/public'; @@ -202,12 +201,10 @@ export function createFlyoutManageDrilldowns({ } function useCompatibleActionFactoriesForCurrentContext( - actionFactories: Array>, + actionFactories: AnyActionFactory[], context: Context ) { - const [compatibleActionFactories, setCompatibleActionFactories] = useState< - Array> - >(); + const [compatibleActionFactories, setCompatibleActionFactories] = useState(); useEffect(() => { let canceled = false; async function updateCompatibleFactoriesForContext() { From f2d9056ddc5705ed58905d4dd8041ea2446992ea Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 13 Mar 2020 12:53:13 +0100 Subject: [PATCH 28/79] bunch of nit ui fixes --- .../action_wizard/action_wizard.scss | 1 + .../public/components/action_wizard/i18n.ts | 2 +- .../connected_flyout_manage_drilldowns.tsx | 6 +-- .../components/flyout_frame/flyout_frame.tsx | 44 +++++++------------ .../list_manage_drilldowns.story.tsx | 4 +- .../list_manage_drilldowns.tsx | 24 ++++++++-- 6 files changed, 44 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss index 2ba6f9baca90d..db09ff4a57ef9 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss @@ -1,6 +1,7 @@ .auaActionWizard__selectedActionFactoryContainer { background-color: $euiColorLightestShade; padding: $euiSize; + border-radius: $euiBorderRadius; } .auaActionWizard__actionFactoryItem { diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts index 641f25176264a..a315184bf68ef 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/i18n.ts @@ -9,6 +9,6 @@ import { i18n } from '@kbn/i18n'; export const txtChangeButton = i18n.translate( 'xpack.advancedUiActions.components.actionWizard.changeButton', { - defaultMessage: 'change', + defaultMessage: 'Change', } ); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index aad31191ca735..866fde9f3a1d0 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -118,12 +118,12 @@ export function createFlyoutManageDrilldowns({ function mapToDrilldownToDrilldownListItem( drilldown: UiActionsSerializedEvent ): DrilldownListItem { + const actionFactory = allActionFactoriesById[drilldown.action.factoryId]; return { id: drilldown.eventId, drilldownName: drilldown.action.name, - actionName: - allActionFactoriesById[drilldown.action.factoryId]?.getDisplayName(props.context) ?? - drilldown.action.factoryId, + actionName: actionFactory?.getDisplayName(props.context) ?? drilldown.action.factoryId, + icon: actionFactory?.getIconType(props.context), }; } diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index 659fc895b19a5..fc803af16a266 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -38,36 +38,24 @@ export const FlyoutFrame: React.FC = ({ }) => { const headerFragment = (title || onBack) && ( - - <> - {/* just title */} - {title && !onBack &&

{title}

} - {/* just back button */} - {!title && onBack && ( - + + + {onBack && ( + + + )} - {/* back button && title */} - {title && onBack && ( - - - - - -

{title}

-
-
+ {title && ( + +

{title}

+
)} - +
); diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx index f700b2bdc3697..eafe50bab2016 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.story.tsx @@ -11,8 +11,8 @@ import { ListManageDrilldowns } from './list_manage_drilldowns'; storiesOf('components/ListManageDrilldowns', module).add('default', () => ( diff --git a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx index c27ff7bd5b6bf..5a15781a1faf2 100644 --- a/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/list_manage_drilldowns/list_manage_drilldowns.tsx @@ -9,13 +9,17 @@ import { EuiBasicTableColumn, EuiButton, EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, EuiSpacer, + EuiTextColor, } from '@elastic/eui'; import React, { useState } from 'react'; import { - txtEditDrilldown, txtCreateDrilldown, txtDeleteDrilldowns, + txtEditDrilldown, txtSelectDrilldown, } from './i18n'; @@ -23,6 +27,7 @@ export interface DrilldownListItem { id: string; actionName: string; drilldownName: string; + icon?: string; } export interface ListManageDrilldownsProps { @@ -50,13 +55,25 @@ export function ListManageDrilldowns({ field: 'drilldownName', name: 'Name', truncateText: true, + width: '50%', }, { - field: 'actionName', name: 'Action', - truncateText: true, + render: (drilldown: DrilldownListItem) => ( + + {drilldown.icon && ( + + + + )} + + {drilldown.actionName} + + + ), }, { + align: 'right', render: (drilldown: DrilldownListItem) => ( onEdit(drilldown.id)}> {txtEditDrilldown} @@ -72,6 +89,7 @@ export function ListManageDrilldowns({ itemId="id" columns={columns} isSelectable={true} + responsive={false} selection={{ onSelectionChange: selection => { setSelectedDrilldowns(selection.map(drilldown => drilldown.id)); From 8ad5cd451e1cdf16647b347ddae834a200f48167 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 13 Mar 2020 13:23:42 +0100 Subject: [PATCH 29/79] Drilldowns reactive action manager (#60099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 improve isConfigValid return type * feat: 🎸 make DynamicActionManager reactive * docs: ✏️ add JSDocs to public mehtods of DynamicActionManager * feat: 🎸 make panel top-right corner number badge reactive * fix: 🐛 correctly await for .deleteEvents() --- .../public/lib/panel/embeddable_panel.tsx | 12 +- .../lib/panel/panel_header/panel_header.tsx | 2 +- .../common/state_containers/types.ts | 2 +- src/plugins/kibana_utils/index.ts | 20 ++ .../public/actions/dynamic_action_manager.ts | 193 ++++++++++++++---- .../actions/dynamic_action_manager_state.ts | 95 +++++++++ .../ui_actions/public/util/configurable.ts | 6 +- .../drilldown.tsx | 2 +- 8 files changed, 284 insertions(+), 48 deletions(-) create mode 100644 src/plugins/kibana_utils/index.ts create mode 100644 src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 980b17ff17f1c..91846e45525fc 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -81,6 +81,7 @@ export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; private subscription?: Subscription; + private drilldownCountSubscription?: Subscription; private mounted: boolean = false; private generateId = htmlIdGenerator(); @@ -154,6 +155,9 @@ export class EmbeddablePanel extends React.Component { if (this.subscription) { this.subscription.unsubscribe(); } + if (this.drilldownCountSubscription) { + this.drilldownCountSubscription.unsubscribe(); + } if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } @@ -210,10 +214,10 @@ export class EmbeddablePanel extends React.Component { const dynamicActions = this.props.embeddable.dynamicActions; if (dynamicActions) { - dynamicActions.count().then(drilldownCount => { - if (this.mounted) { - this.setState({ drilldownCount }); - } + this.setState({ drilldownCount: dynamicActions.state.get().events.length }); + this.drilldownCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { + if (!this.mounted) return; + this.setState({ drilldownCount: events.length }); }); } } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 6a6a3510602d1..ed1ae739502a7 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -150,7 +150,7 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - {!isViewMode && drilldownCount && ( + {!isViewMode && !!drilldownCount && ( {drilldownCount} diff --git a/src/plugins/kibana_utils/common/state_containers/types.ts b/src/plugins/kibana_utils/common/state_containers/types.ts index 26a29bc470e8a..29ffa4cd486b5 100644 --- a/src/plugins/kibana_utils/common/state_containers/types.ts +++ b/src/plugins/kibana_utils/common/state_containers/types.ts @@ -43,7 +43,7 @@ export interface BaseStateContainer { export interface StateContainer< State extends BaseState, - PureTransitions extends object, + PureTransitions extends object = object, PureSelectors extends object = {} > extends BaseStateContainer { transitions: Readonly>; diff --git a/src/plugins/kibana_utils/index.ts b/src/plugins/kibana_utils/index.ts new file mode 100644 index 0000000000000..f15c7cfb69743 --- /dev/null +++ b/src/plugins/kibana_utils/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createStateContainer, StateContainer } from './common'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index fbf1092de29af..dd3d39b53de66 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -22,6 +22,8 @@ import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; import { UiActionsService } from '../service'; import { SerializedAction } from './types'; import { ActionDefinition } from './action'; +import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; +import { StateContainer, createStateContainer } from '../../../kibana_utils'; export interface DynamicActionManagerParams { storage: ActionStorage; @@ -37,21 +39,84 @@ export class DynamicActionManager { private readonly idPrefix = `D_ACTION_${DynamicActionManager.idPrefixCounter++}_`; + private stopped: boolean = false; + + /** + * UI State of the dynamic action manager. + */ + protected readonly ui = createStateContainer(defaultState, transitions, selectors); + constructor(protected readonly params: DynamicActionManagerParams) {} + protected getEvent(eventId: string): SerializedEvent { + const oldEvent = this.ui.selectors.getEvent(eventId); + if (!oldEvent) throw new Error(`Could not find event [eventId = ${eventId}].`); + return oldEvent; + } + + /** + * We prefix action IDs with a unique `.idPrefix`, so we can render the + * same dashboard twice on the screen. + */ protected generateActionId(eventId: string): string { return this.idPrefix + eventId; } + protected reviveAction(event: SerializedEvent) { + const { eventId, triggerId, action } = event; + const { uiActions, isCompatible } = this.params; + const { name } = action; + + const actionId = this.generateActionId(eventId); + const factory = uiActions.getActionFactory(event.action.factoryId); + const actionDefinition: ActionDefinition = { + ...factory.create(action as SerializedAction), + id: actionId, + isCompatible, + getDisplayName: () => name, + getIconType: context => factory.getIconType(context), + }; + + uiActions.addTriggerAction(triggerId as any, actionDefinition); + } + + protected killAction({ eventId, triggerId }: SerializedEvent) { + const { uiActions } = this.params; + const actionId = this.generateActionId(eventId); + uiActions.removeTriggerAction(triggerId as any, actionId); + } + + // Public API: --------------------------------------------------------------- + + /** + * Read-only state container of dynamic action manager. Use it to perform all + * *read* operations. + */ + public readonly state: StateContainer = this.ui; + + /** + * 1. Loads all events from @type {DynamicActionStorage} storage. + * 2. Creates actions for each event in `ui_actions` registry. + * 3. Adds events to UI state. + * 4. Does nothing if dynamic action manager was stopped of if event fetching + * is already taking place. + */ public async start() { - const events = await this.params.storage.list(); + if (this.stopped) return; + if (this.ui.get().isFetchingEvents) return; - for (const event of events) { - this.reviveAction(event); - } + this.ui.transitions.startFetching(); + const events = await this.params.storage.list(); + for (const event of events) this.reviveAction(event); + this.ui.transitions.finishFetching(events); } + /** + * 1. Removes all events from `ui_actions` registry. + * 2. Puts dynamic action manager is stopped state. + */ public async stop() { + this.stopped = true; const events = await this.params.storage.list(); for (const event of events) { @@ -59,6 +124,18 @@ export class DynamicActionManager { } } + /** + * Creates a new event. + * + * 1. Stores event in @type {DynamicActionStorage} storage. + * 2. Optimistically adds it to UI state, and rolls back on failure. + * 3. Adds action to `ui_actions` registry. + * + * @todo `triggerId` should not be optional. + * + * @param action Dynamic action for which to create an event. + * @param triggerId Trigger to which to attach the action. + */ public async createEvent(action: SerializedAction, triggerId = 'VALUE_CLICK_TRIGGER') { const event: SerializedEvent = { eventId: uuidv4(), @@ -66,10 +143,29 @@ export class DynamicActionManager { action, }; - await this.params.storage.create(event); - this.reviveAction(event); + this.ui.transitions.addEvent(event); + try { + await this.params.storage.create(event); + this.reviveAction(event); + } catch { + this.ui.transitions.removeEvent(event.eventId); + } } + /** + * Updates an existing event. Fails if event with given `eventId` does not + * exit. + * + * 1. Updates the event in @type {DynamicActionStorage} storage. + * 2. Optimistically replaces the old event by the new one in UI state, and + * rolls back on failure. + * 3. Replaces action in `ui_actions` registry with the new event. + * + * + * @param eventId ID of the event to replace. + * @param action New action for which to create the event. + * @param triggerId New trigger with which to associate the event. + */ public async updateEvent( eventId: string, action: SerializedAction, @@ -81,49 +177,68 @@ export class DynamicActionManager { action, }; - const oldEvent = await this.params.storage.read(eventId); + const oldEvent = this.getEvent(eventId); this.killAction(oldEvent); - await this.params.storage.update(event); + this.reviveAction(event); - } + this.ui.transitions.replaceEvent(event); - public async deleteEvents(eventIds: string[]) { - const eventsToKill = (await this.params.storage.list()).filter(event => - eventIds.includes(event.eventId) - ); - await Promise.all(eventIds.map(eventId => this.params.storage.remove(eventId))); - eventsToKill.forEach(event => this.killAction(event)); + try { + await this.params.storage.update(event); + } catch { + this.killAction(event); + this.reviveAction(oldEvent); + this.ui.transitions.replaceEvent(oldEvent); + } } - public async list(): Promise { - return await this.params.storage.list(); + /** + * Removes existing event. Throws if event does not exist. + * + * 1. Removes the event from @type {DynamicActionStorage} storage. + * 2. Optimistically removes event from UI state, and puts it back on failure. + * 3. Removes associated action from `ui_actions` registry. + * + * @param eventId ID of the event to remove. + */ + public async deleteEvent(eventId: string) { + const event = this.getEvent(eventId); + + this.killAction(event); + this.ui.transitions.removeEvent(eventId); + + try { + await this.params.storage.remove(eventId); + } catch { + this.reviveAction(event); + this.ui.transitions.addEvent(event); + } } - public async count(): Promise { - return await this.params.storage.count(); + /** + * Deletes multiple events at once. + * + * @param eventIds List of event IDs. + */ + public async deleteEvents(eventIds: string[]) { + await Promise.all(eventIds.map(this.deleteEvent.bind(this))); } - protected reviveAction(event: SerializedEvent) { - const { eventId, triggerId, action } = event; - const { uiActions, isCompatible } = this.params; - const { name } = action; - - const actionId = this.generateActionId(eventId); - const factory = uiActions.getActionFactory(event.action.factoryId); - const actionDefinition: ActionDefinition = { - ...factory.create(action as SerializedAction), - id: actionId, - isCompatible, - getDisplayName: () => name, - getIconType: context => factory.getIconType(context), - }; - - uiActions.addTriggerAction(triggerId as any, actionDefinition); + /** + * @deprecated + * + * Use `.state.get().events` instead. + */ + public async list(): Promise { + return this.state.get().events; } - protected killAction({ eventId, triggerId }: SerializedEvent) { - const { uiActions } = this.params; - const actionId = this.generateActionId(eventId); - uiActions.removeTriggerAction(triggerId as any, actionId); + /** + * @deprecated + * + * Use `.state.get().events.length` instead. + */ + public async count(): Promise { + return this.state.get().events.length; } } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts new file mode 100644 index 0000000000000..2d767ebe18968 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SerializedEvent } from './dynamic_action_storage'; + +/** + * This interface represents the state of @type {DynamicActionManager} at any + * point in time. + */ +export interface State { + /** + * Whether dynamic action manager is currently in process of fetching events + * from storage. + */ + readonly isFetchingEvents: boolean; + + /** + * Number of times event fetching has been completed. + */ + readonly fetchCount: number; + + /** + * List of all fetched events. If `null`, means now events have been loaded yet. + */ + readonly events: readonly SerializedEvent[]; +} + +export interface Transitions { + startFetching: (state: State) => () => State; + finishFetching: (state: State) => (events: SerializedEvent[]) => State; + addEvent: (state: State) => (event: SerializedEvent) => State; + removeEvent: (state: State) => (eventId: string) => State; + replaceEvent: (state: State) => (event: SerializedEvent) => State; +} + +export interface Selectors { + getEvent: (state: State) => (eventId: string) => SerializedEvent | null; +} + +export const defaultState: State = { + isFetchingEvents: false, + fetchCount: 0, + events: [], +}; + +export const transitions: Transitions = { + startFetching: state => () => ({ ...state, isFetchingEvents: true }), + + finishFetching: state => events => ({ + ...state, + isFetchingEvents: false, + fetchCount: state.fetchCount + 1, + events, + }), + + addEvent: state => (event: SerializedEvent) => ({ + ...state, + events: [...(state.events || []), event], + }), + + removeEvent: state => (eventId: string) => ({ + ...state, + events: state.events ? state.events.filter(event => event.eventId !== eventId) : state.events, + }), + + replaceEvent: state => event => { + const index = state.events.findIndex(({ eventId }) => eventId === event.eventId); + if (index === -1) return state; + + return { + ...state, + events: [...state.events.slice(0, index), event, ...state.events.slice(index + 1)], + }; + }, +}; + +export const selectors: Selectors = { + getEvent: state => eventId => state.events.find(event => event.eventId === eventId) || null, +}; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index d2586db52ec9f..ea13bab4d2e5e 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -31,9 +31,11 @@ export interface Configurable Config; /** - * Is this config valid. Used to validate user's input before saving + * Is this config valid. Used to validate user's input before saving. + * + * @todo Rename this to `isConfig`? */ - readonly isConfigValid: (config: Config) => boolean; + readonly isConfigValid: (config: Config) => config is Config; /** * `UiComponent` to be rendered when collecting configuration for this item. diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index c839ef8ee04ef..211a16451b30c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -48,7 +48,7 @@ export class DashboardToDashboardDrilldown useCurrentDashboardFilters: true, }); - public readonly isConfigValid = (config: Config) => { + public readonly isConfigValid = (config: Config): config is Config => { if (!config.dashboardId) return false; return true; }; From 6f59d1a0e2ec97be88b3b2c32eea430f2d702cfb Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 13 Mar 2020 14:56:00 +0100 Subject: [PATCH 30/79] Drilldowns various 2 (#60103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 address review comments * test: 💍 fix embeddable_panel.test.tsx tests * chore: 🤖 clean up ActionInternal * chore: 🤖 make isConfigValid a simple predicate * chore: 🤖 fix TypeScript type errors --- .../lib/panel/embeddable_panel.test.tsx | 2 +- .../public/actions/action_internal.test.ts | 71 ------------------- .../public/actions/action_internal.ts | 23 ------ .../actions/dynamic_action_manager_state.ts | 4 +- .../ui_actions/public/util/configurable.ts | 4 +- .../connected_flyout_manage_drilldowns.tsx | 2 +- .../test_data.ts | 4 ++ 7 files changed, 9 insertions(+), 101 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 505399f3150be..8abd2d9cab5a6 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -244,7 +244,7 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { const fooContextMenuActionItem1 = findTestSubject(component1, 'embeddablePanelAction-FOO'); const fooContextMenuActionItem2 = findTestSubject(component2, 'embeddablePanelAction-FOO'); - expect(fooContextMenuActionItem1.length).toBe(1); + expect(fooContextMenuActionItem1.length).toBe(2); expect(fooContextMenuActionItem2.length).toBe(0); }); diff --git a/src/plugins/ui_actions/public/actions/action_internal.test.ts b/src/plugins/ui_actions/public/actions/action_internal.test.ts index 2a2b639434ac2..b14346180c274 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.test.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.test.ts @@ -30,75 +30,4 @@ describe('ActionInternal', () => { const action = new ActionInternal(defaultActionDef); expect(action.id).toBe('test-action'); }); - - describe('serialize()', () => { - test('can serialize very simple action', () => { - const action = new ActionInternal(defaultActionDef); - - action.config = {}; - - const serialized = action.serialize(); - - expect(serialized).toMatchObject({ - id: 'test-action', - name: '', - config: expect.any(Object), - }); - }); - - test('can serialize action with modified state', () => { - const action = new ActionInternal({ - ...defaultActionDef, - type: 'ACTION_TYPE' as any, - order: 11, - }); - - action.name = 'qux'; - action.config = { foo: 'bar' }; - - const serialized = action.serialize(); - - expect(serialized).toMatchObject({ - id: 'test-action', - factoryId: 'ACTION_TYPE', - name: 'qux', - config: { - foo: 'bar', - }, - }); - }); - }); - - describe('deserialize', () => { - const serialized = { - id: 'id', - factoryId: 'type', - name: 'name', - config: { - foo: 'foo', - }, - }; - - test('can deserialize action state', () => { - const action = new ActionInternal({ - ...defaultActionDef, - }); - - action.deserialize(serialized); - - expect(action.name).toBe('name'); - expect(action.config).toMatchObject(serialized.config); - }); - - test('does not overwrite action id and type', () => { - const action = new ActionInternal({ - ...defaultActionDef, - }); - - action.deserialize(serialized); - - expect(action.id).toBe('test-action'); - expect(action.type).toBe(''); - }); - }); }); diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index ef27b78464f2b..b6652f7e8a50a 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -21,7 +21,6 @@ import { Action, ActionContext as Context, AnyActionDefinition } from './action' import { Presentable } from '../util/presentable'; import { uiToReactComponent } from '../../../kibana_react/public'; import { ActionType } from '../types'; -import { SerializedAction } from './types'; export class ActionInternal implements Action>, Presentable> { @@ -33,9 +32,6 @@ export class ActionInternal public readonly MenuItem? = this.definition.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - public name: string = ''; - public config?: object; - public execute(context: Context) { return this.definition.execute(context); } @@ -59,25 +55,6 @@ export class ActionInternal if (!this.definition.getHref) return undefined; return this.definition.getHref(context); } - - public serialize() { - if (!this.config) { - throw new Error('Action does not have a config.'); - } - - const serialized: SerializedAction = { - factoryId: this.type, - name: this.name, - config: this.config, - }; - - return serialized; - } - - public deserialize({ name, config }: SerializedAction) { - this.name = name; - this.config = config as object; - } } export type AnyActionInternal = ActionInternal; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts index 2d767ebe18968..ba42fc4d15ce7 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts @@ -36,7 +36,7 @@ export interface State { readonly fetchCount: number; /** - * List of all fetched events. If `null`, means now events have been loaded yet. + * List of all fetched events. */ readonly events: readonly SerializedEvent[]; } @@ -71,7 +71,7 @@ export const transitions: Transitions = { addEvent: state => (event: SerializedEvent) => ({ ...state, - events: [...(state.events || []), event], + events: [...state.events, event], }), removeEvent: state => (eventId: string) => ({ diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index ea13bab4d2e5e..323bacd033bd5 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -32,10 +32,8 @@ export interface Configurable config is Config; + readonly isConfigValid: (config: Config) => boolean; /** * `UiComponent` to be rendered when collecting configuration for this item. diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 866fde9f3a1d0..dbed6da46ba46 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -242,7 +242,7 @@ function useDrilldownsStateManager( notifications: NotificationsStart ) { const [isLoading, setIsLoading] = useState(false); - const [drilldowns, setDrilldowns] = useState(); + const [drilldowns, setDrilldowns] = useState(); const isMounted = useMountedState(); async function run(op: () => Promise) { diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index a4d09dddbaf31..aaf9a0e3dd020 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -49,6 +49,10 @@ class MockDynamicActionManager implements PublicMethodsOf }; } + async deleteEvent() { + throw new Error('not implemented'); + } + async start() {} async stop() {} } From 66e791eda1c0af8ce65a6624ad9210d50ed6d0c7 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 13 Mar 2020 15:08:44 +0100 Subject: [PATCH 31/79] =?UTF-8?q?test:=20=F0=9F=92=8D=20stub=20DynamicActi?= =?UTF-8?q?onManager=20tests=20(#60104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/dynamic_action_manager.test.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts new file mode 100644 index 0000000000000..aebd451ae11b0 --- /dev/null +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DynamicActionManager, DynamicActionManagerParams } from './dynamic_action_manager'; + +const setup = () => { + const isCompatible = async () => true; + const storage: DynamicActionManagerParams['storage'] = { + count: jest.fn(), + create: jest.fn(), + list: jest.fn(), + read: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + }; + const uiActions: DynamicActionManagerParams['uiActions'] = { + addTriggerAction: jest.fn(), + getActionFactory: jest.fn(), + removeTriggerAction: jest.fn(), + }; + const manager = new DynamicActionManager({ + isCompatible, + storage, + uiActions, + }); + + return { + isCompatible, + storage, + uiActions, + manager, + }; +}; + +describe('DynamicActionManager', () => { + test('can instantiate', () => { + const { manager } = setup(); + expect(manager).toBeInstanceOf(DynamicActionManager); + }); + + describe('.start()', () => { + test.todo('instantiates stored events'); + test.todo('does nothing when no events stored'); + }); + + describe('.stop()', () => { + test.todo('removes events from UI actions registry'); + test.todo('does nothing when no events stored'); + }); + + describe('.createEvent()', () => { + test.todo('stores new event in storage'); + test.todo('instantiates event in actions service'); + }); + + describe('.updateEvent()', () => { + test.todo('removes old event from ui actions service'); + test.todo('updates event in storage'); + test.todo('adds new event to ui actions service'); + }); + + describe('.deleteEvents()', () => { + test.todo('removes all actions from ui actions service'); + test.todo('removes all events from storage'); + describe('when event is removed from storage its action is also killed', () => { + test.todo('when subsequent event fails to be removed from storage'); + }); + }); + + describe('.list()', () => { + test.todo('returns stored events'); + }); + + describe('.count()', () => { + test.todo('returns number of stored events'); + }); +}); From 1f238fef65ccdab7c4acadbaa23ab498b20ab442 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 13 Mar 2020 19:22:03 +0100 Subject: [PATCH 32/79] Drilldowns review 1 (#60139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 improve generic types * fix: 🐛 don't overwrite icon * fix: 🐛 fix x-pack TypeScript errors * fix: 🐛 fix TypeScript error --- .../ui_actions/public/actions/action.ts | 1 - .../public/actions/action_factory.ts | 62 +++++++++---------- .../actions/action_factory_definition.ts | 14 ----- .../public/actions/action_internal.ts | 6 +- .../public/actions/dynamic_action_manager.ts | 1 - .../action_identifier.tsx | 4 +- .../error_configure_action.tsx | 4 +- .../public/service/ui_actions_service.ts | 17 +++-- src/plugins/ui_actions/public/types.ts | 6 +- .../ui_actions/public/util/configurable.ts | 10 +-- .../action_wizard/action_wizard.tsx | 14 ++--- .../components/action_wizard/test_data.tsx | 12 ++-- .../advanced_ui_actions/public/index.ts | 2 - .../action_factory_service/action_factory.ts | 2 +- .../action_factory_definition.ts | 3 +- .../connected_flyout_manage_drilldowns.tsx | 10 +-- .../flyout_drilldown_wizard.story.tsx | 2 +- .../flyout_drilldown_wizard.tsx | 10 +-- .../form_drilldown_wizard.tsx | 8 +-- 19 files changed, 78 insertions(+), 110 deletions(-) diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index a7bbd9f116f39..15f1d6dd79289 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -91,5 +91,4 @@ export interface ActionDefinition execute(context: Context): Promise; } -export type AnyActionDefinition = ActionDefinition; export type ActionContext = A extends ActionDefinition ? Context : never; diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/src/plugins/ui_actions/public/actions/action_factory.ts index 3f00121a9c577..ae20e8b924b19 100644 --- a/src/plugins/ui_actions/public/actions/action_factory.ts +++ b/src/plugins/ui_actions/public/actions/action_factory.ts @@ -20,54 +20,52 @@ import { uiToReactComponent } from '../../../kibana_react/public'; import { Presentable } from '../util/presentable'; import { ActionDefinition } from './action'; -import { - AnyActionFactoryDefinition, - AFDConfig as Config, - AFDFactoryContext as FactoryContext, - AFDActionContext as ActionContext, -} from './action_factory_definition'; +import { ActionFactoryDefinition } from './action_factory_definition'; import { Configurable } from '../util'; import { SerializedAction } from './types'; -export class ActionFactory - implements Presentable>, Configurable> { - constructor(public readonly definition: D) {} +export class ActionFactory< + Config extends object = object, + FactoryContext extends object = object, + ActionContext extends object = object +> implements Presentable, Configurable { + constructor( + protected readonly def: ActionFactoryDefinition + ) {} - public readonly id = this.definition.id; - public readonly order = this.definition.order || 0; - public readonly MenuItem? = this.definition.MenuItem; + public readonly id = this.def.id; + public readonly order = this.def.order || 0; + public readonly MenuItem? = this.def.MenuItem; public readonly ReactMenuItem? = this.MenuItem ? uiToReactComponent(this.MenuItem) : undefined; - public readonly CollectConfig = this.definition.CollectConfig; + public readonly CollectConfig = this.def.CollectConfig; public readonly ReactCollectConfig = uiToReactComponent(this.CollectConfig); - public readonly createConfig = this.definition.createConfig; - public readonly isConfigValid = this.definition.isConfigValid; + public readonly createConfig = this.def.createConfig; + public readonly isConfigValid = this.def.isConfigValid; - public getIconType(context: FactoryContext): string | undefined { - if (!this.definition.getIconType) return undefined; - return this.definition.getIconType(context); + public getIconType(context: FactoryContext): string | undefined { + if (!this.def.getIconType) return undefined; + return this.def.getIconType(context); } - public getDisplayName(context: FactoryContext): string { - if (!this.definition.getDisplayName) return ''; - return this.definition.getDisplayName(context); + public getDisplayName(context: FactoryContext): string { + if (!this.def.getDisplayName) return ''; + return this.def.getDisplayName(context); } - public async isCompatible(context: FactoryContext): Promise { - if (!this.definition.isCompatible) return true; - return await this.definition.isCompatible(context); + public async isCompatible(context: FactoryContext): Promise { + if (!this.def.isCompatible) return true; + return await this.def.isCompatible(context); } - public getHref(context: FactoryContext): string | undefined { - if (!this.definition.getHref) return undefined; - return this.definition.getHref(context); + public getHref(context: FactoryContext): string | undefined { + if (!this.def.getHref) return undefined; + return this.def.getHref(context); } public create( - serializedAction: Omit>, 'factoryId'> - ): ActionDefinition> { - return this.definition.create(serializedAction); + serializedAction: Omit, 'factoryId'> + ): ActionDefinition { + return this.def.create(serializedAction); } } - -export type AnyActionFactory = ActionFactory; diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts index eaeca9fd4383e..8cc9215061e19 100644 --- a/src/plugins/ui_actions/public/actions/action_factory_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_factory_definition.ts @@ -44,17 +44,3 @@ export interface ActionFactoryDefinition< serializedAction: Omit, 'factoryId'> ): ActionDefinition; } - -export type AnyActionFactoryDefinition = ActionFactoryDefinition; - -export type AFDConfig = T extends ActionFactoryDefinition - ? Config - : never; - -export type AFDFactoryContext = T extends ActionFactoryDefinition - ? FactoryContext - : never; - -export type AFDActionContext = T extends ActionFactoryDefinition - ? ActionContext - : never; diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index b6652f7e8a50a..245ded991c032 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -17,12 +17,12 @@ * under the License. */ -import { Action, ActionContext as Context, AnyActionDefinition } from './action'; +import { Action, ActionContext as Context, ActionDefinition } from './action'; import { Presentable } from '../util/presentable'; import { uiToReactComponent } from '../../../kibana_react/public'; import { ActionType } from '../types'; -export class ActionInternal +export class ActionInternal implements Action>, Presentable> { constructor(public readonly definition: A) {} @@ -56,5 +56,3 @@ export class ActionInternal return this.definition.getHref(context); } } - -export type AnyActionInternal = ActionInternal; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index dd3d39b53de66..ae6c97fcc78df 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -74,7 +74,6 @@ export class DynamicActionManager { id: actionId, isCompatible, getDisplayName: () => name, - getIconType: context => factory.getIconType(context), }; uiActions.addTriggerAction(triggerId as any, actionDefinition); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx b/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx index 94c3f7b96b8c1..969e3dbac0e67 100644 --- a/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx +++ b/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx @@ -19,10 +19,10 @@ import React from 'react'; import { EuiCode } from '@elastic/eui'; -import { AnyActionInternal } from '../../actions'; +import { ActionInternal } from '../../actions'; export interface ActionIdentifierProps { - action: AnyActionInternal; + action: ActionInternal; } export const ActionIdentifier: React.FC = ({ action }) => ( diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx index 09fcfcd1d19db..93ef139cf1940 100644 --- a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx +++ b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx @@ -20,12 +20,12 @@ import React from 'react'; import { EuiCallOut } from '@elastic/eui'; import { txtSorryActionConfigurationError } from './i18n'; -import { AnyActionInternal } from '../../actions'; import { ActionIdentifier } from './action_identifier'; +import { ActionInternal } from '../../actions'; export interface ErrorConfigureActionProps { msg?: React.ReactNode; - action?: AnyActionInternal; + action?: ActionInternal; } export const ErrorConfigureAction: React.FC = ({ diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 7f6c2a2d41572..824be69a991a8 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -28,14 +28,11 @@ import { } from '../types'; import { ActionInternal, - AnyActionInternal, Action, ActionByType, - AnyActionDefinition, - AnyActionFactoryDefinition, ActionFactory, - AnyActionFactory, ActionDefinition, + ActionFactoryDefinition, } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -91,7 +88,7 @@ export class UiActionsService { return trigger.contract; }; - public readonly registerAction = ( + public readonly registerAction = ( definition: A ): ActionInternal => { if (this.actions.has(definition.id)) { @@ -147,7 +144,7 @@ export class UiActionsService { } }; - public readonly getAction = (id: string): ActionInternal => { + public readonly getAction = (id: string): ActionInternal => { if (!this.actions.has(id)) { throw new Error(`Action [action.id = ${id}] not registered.`); } @@ -212,7 +209,7 @@ export class UiActionsService { const actionIds = this.triggerToActions.get(triggerId); const actions = actionIds! - .map(actionId => this.actions.get(actionId) as AnyActionInternal) + .map(actionId => this.actions.get(actionId) as ActionInternal) .filter(Boolean); return actions as Array>>; @@ -276,7 +273,7 @@ export class UiActionsService { * Register an action factory. Action factories are used to configure and * serialize/deserialize dynamic actions. */ - public readonly registerActionFactory = (definition: AnyActionFactoryDefinition) => { + public readonly registerActionFactory = (definition: ActionFactoryDefinition) => { if (this.actionFactories.has(definition.id)) { throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); } @@ -286,7 +283,7 @@ export class UiActionsService { this.actionFactories.set(actionFactory.id, actionFactory); }; - public readonly getActionFactory = (actionFactoryId: string): AnyActionFactory => { + public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { const actionFactory = this.actionFactories.get(actionFactoryId); if (!actionFactory) { @@ -299,7 +296,7 @@ export class UiActionsService { /** * Returns an array of all action factories. */ - public readonly getActionFactories = (): AnyActionFactory[] => { + public readonly getActionFactories = (): ActionFactory[] => { return [...this.actionFactories.values()]; }; } diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index c2e5a1668f7f4..9fab2a7785793 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -19,12 +19,12 @@ import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; -import { AnyActionFactory } from './actions/action_factory'; +import { ActionFactory } from './actions'; export type TriggerRegistry = Map>; -export type ActionRegistry = Map>; +export type ActionRegistry = Map; export type TriggerToActionsRegistry = Map; -export type ActionFactoryRegistry = Map; +export type ActionFactoryRegistry = Map; const DEFAULT_TRIGGER = ''; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index 323bacd033bd5..7d2ceb61c106e 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -19,12 +19,10 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; -export type ConfigurableBaseConfig = object; - /** * Represents something that can be configured by user using UI. */ -export interface Configurable { +export interface Configurable { /** * Create default config for this item, used when item is created for the first time. */ @@ -33,7 +31,7 @@ export interface Configurable boolean; + readonly isConfigValid: (config: Config) => config is Config; /** * `UiComponent` to be rendered when collecting configuration for this item. @@ -44,9 +42,7 @@ export interface Configurable { +export interface CollectConfigProps { /** * Current (latest) config of the item. */ diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 5d605ca1c3b7f..f6fcbba7f4a9a 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; -import { AnyActionFactory } from '../../services'; +import { ActionFactory } from '../../services'; type ActionBaseConfig = object; type ActionFactoryBaseContext = object; @@ -25,19 +25,19 @@ export interface ActionWizardProps { /** * List of available action factories */ - actionFactories: AnyActionFactory[]; + actionFactories: ActionFactory[]; /** * Currently selected action factory * undefined - is allowed and means that non is selected */ - currentActionFactory?: AnyActionFactory; + currentActionFactory?: ActionFactory; /** * Action factory selected changed * null - means user click "change" and removed action factory selection */ - onActionFactoryChange: (actionFactory: AnyActionFactory | null) => void; + onActionFactoryChange: (actionFactory: ActionFactory | null) => void; /** * current config for currently selected action factory @@ -97,7 +97,7 @@ export const ActionWizard: React.FC = ({ }; interface SelectedActionFactoryProps { - actionFactory: AnyActionFactory; + actionFactory: ActionFactory; config: ActionBaseConfig; context: ActionFactoryBaseContext; onConfigChange: (config: ActionBaseConfig) => void; @@ -153,9 +153,9 @@ const SelectedActionFactory: React.FC = ({ }; interface ActionFactorySelectorProps { - actionFactories: AnyActionFactory[]; + actionFactories: ActionFactory[]; context: ActionFactoryBaseContext; - onActionFactorySelected: (actionFactory: AnyActionFactory) => void; + onActionFactorySelected: (actionFactory: ActionFactory) => void; } export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 56b9dabaec001..3787c1651a586 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { ActionWizard } from './action_wizard'; -import { ActionFactoryDefinition, AnyActionFactory, ActionFactory } from '../../services'; +import { ActionFactoryDefinition, ActionFactory } from '../../services'; import { CollectConfigProps } from '../../util'; type ActionBaseConfig = object; @@ -88,7 +88,7 @@ export const dashboardDrilldownActionFactory: ActionFactoryDefinition< useCurrentDashboardFilters: true, }; }, - isConfigValid: config => { + isConfigValid: (config: DashboardDrilldownConfig): config is DashboardDrilldownConfig => { if (!config.dashboardId) return false; return true; }, @@ -146,7 +146,7 @@ export const urlDrilldownActionFactory: ActionFactoryDefinition { + isConfigValid: (config: any): config is UrlDrilldownConfig => { if (!config.url) return false; return true; }, @@ -161,13 +161,13 @@ export const urlDrilldownActionFactory: ActionFactoryDefinition> }) { const [state, setState] = useState<{ - currentActionFactory?: AnyActionFactory; + currentActionFactory?: ActionFactory; config?: ActionBaseConfig; }>({}); - function changeActionFactory(newActionFactory: AnyActionFactory | null) { + function changeActionFactory(newActionFactory: ActionFactory | null) { if (!newActionFactory) { // removing action factory return setState({}); diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index cbe7b0c26db3b..55361105dcb0f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -20,9 +20,7 @@ export { export { ActionWizard } from './components'; export { ActionFactoryDefinition as AdvancedUiActionsActionFactoryDefinition, - AnyActionFactoryDefinition as AdvancedUiActionsAnyActionFactoryDefinition, ActionFactory as AdvancedUiActionsActionFactory, - AnyActionFactory as AdvancedUiActionsAnyActionFactory, } from './services/action_factory_service'; export { Configurable as AdvancedUiActionsConfigurable, diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts index 955b5c0d70b52..66e2a4eafa880 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts @@ -7,5 +7,5 @@ /* eslint-disable */ export { - ActionFactory, AnyActionFactory + ActionFactory } from '../../../../../../src/plugins/ui_actions/public/actions/action_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts index f29abe6e41b22..f8669a4bf813f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts @@ -7,6 +7,5 @@ /* eslint-disable */ export { - ActionFactoryDefinition, - AnyActionFactoryDefinition, + ActionFactoryDefinition } from '../../../../../../src/plugins/ui_actions/public/actions/action_factory_definition'; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index dbed6da46ba46..9462b89fc26ff 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import useMount from 'react-use/lib/useMount'; import useMountedState from 'react-use/lib/useMountedState'; import { - AdvancedUiActionsAnyActionFactory as AnyActionFactory, + AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, } from '../../../../advanced_ui_actions/public'; import { NotificationsStart } from '../../../../../../src/core/public'; @@ -61,7 +61,7 @@ export function createFlyoutManageDrilldowns({ const allActionFactoriesById = allActionFactories.reduce((acc, next) => { acc[next.id] = next; return acc; - }, {} as Record); + }, {} as Record); return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; @@ -201,10 +201,12 @@ export function createFlyoutManageDrilldowns({ } function useCompatibleActionFactoriesForCurrentContext( - actionFactories: AnyActionFactory[], + actionFactories: Array>, context: Context ) { - const [compatibleActionFactories, setCompatibleActionFactories] = useState(); + const [compatibleActionFactories, setCompatibleActionFactories] = useState< + Array> + >(); useEffect(() => { let canceled = false; async function updateCompatibleFactoriesForContext() { diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx index 25d172f5f83ce..f332bfc3cecba 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -38,7 +38,7 @@ storiesOf('components/FlyoutDrilldownWizard', module) drilldownActionFactories={[urlFactory, dashboardFactory]} initialDrilldownWizardConfig={{ name: 'My fancy drilldown', - actionFactory: urlFactory, + actionFactory: urlFactory as any, actionConfig: { url: 'https://elastic.co', openInNewTab: true, diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index b7ed60084473c..faa965a98a4bb 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -16,20 +16,16 @@ import { txtEditDrilldownTitle, } from './i18n'; import { DrilldownHelloBar } from '../drilldown_hello_bar'; -import { - AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition, - AdvancedUiActionsActionFactory as ActionFactory, - AdvancedUiActionsAnyActionFactory as AnyActionFactory, -} from '../../../../advanced_ui_actions/public'; +import { AdvancedUiActionsActionFactory as ActionFactory } from '../../../../advanced_ui_actions/public'; export interface DrilldownWizardConfig { name: string; - actionFactory?: ActionFactory>; + actionFactory?: ActionFactory; actionConfig?: ActionConfig; } export interface FlyoutDrilldownWizardProps { - drilldownActionFactories: AnyActionFactory[]; + drilldownActionFactories: Array>; onSubmit?: (drilldownWizardConfig: Required) => void; onDelete?: () => void; diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index d1dccccf1edb3..4d9f0f8e13a77 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -9,7 +9,7 @@ import './form_drilldown_wizard.scss'; import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; import { - AdvancedUiActionsAnyActionFactory as AnyActionFactory, + AdvancedUiActionsActionFactory as ActionFactory, ActionWizard, } from '../../../../advanced_ui_actions/public'; @@ -19,14 +19,14 @@ export interface FormDrilldownWizardProps { name?: string; onNameChange?: (name: string) => void; - currentActionFactory?: AnyActionFactory; - onActionFactoryChange?: (actionFactory: AnyActionFactory | null) => void; + currentActionFactory?: ActionFactory; + onActionFactoryChange?: (actionFactory: ActionFactory | null) => void; actionFactoryContext: object; actionConfig?: object; onActionConfigChange?: (config: object) => void; - actionFactories?: AnyActionFactory[]; + actionFactories?: ActionFactory[]; } export const FormDrilldownWizard: React.FC = ({ From e66513cc7dfb37598a42e306bc3c0112938b0ba8 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 16 Mar 2020 10:24:32 +0100 Subject: [PATCH 33/79] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correct=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index fb2ba1f9f340d..e0ac61a55081b 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -43,6 +43,15 @@ export { Configurable as UiActionsConfigurable, CollectConfigProps as UiActionsCollectConfigProps, } from './util'; -export { Trigger, TriggerContext } from './triggers'; +export { + Trigger, + TriggerContext, + SELECT_RANGE_TRIGGER, + selectRangeTrigger, + VALUE_CLICK_TRIGGER, + valueClickTrigger, + APPLY_FILTER_TRIGGER, + applyFilterTrigger, +} from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType, DynamicActionManager as UiActionsDynamicActionManager } from './actions'; From 6efef2b5bcf084ee1ab8f1a46f572940f4be14b6 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 16 Mar 2020 15:02:43 +0100 Subject: [PATCH 34/79] Drilldowns various 4 (#60264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 hide "Create drilldown" from context menu when needed * style: 💄 remove AnyDrilldown type * feat: 🎸 add drilldown factory context * chore: 🤖 remove sample drilldown * fix: 🐛 increase spacing between action factory picker --- src/plugins/ui_actions/public/index.ts | 9 +- .../public/service/ui_actions_service.ts | 12 +- .../action_wizard/action_wizard.tsx | 26 +++-- .../actions/flyout_create_drilldown/index.tsx | 21 ++-- .../dashboard_drilldowns_services.ts | 3 +- .../drilldown.tsx | 5 - .../dashboard_to_dashboard_drilldown/types.ts | 7 +- .../public/services/drilldown_service.ts | 62 +++++----- x-pack/plugins/drilldowns/public/types.ts | 109 ++++++++++++++++-- 9 files changed, 177 insertions(+), 77 deletions(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index e0ac61a55081b..f674179a217aa 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -28,14 +28,15 @@ export { UiActionsSetup, UiActionsStart } from './plugin'; export { UiActionsServiceParams, UiActionsService } from './service'; export { Action, - createAction, - IncompatibleActionError, ActionDefinition as UiActionsActionDefinition, + ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, ActionStorage as UiActionsActionStorage, - SerializedEvent as UiActionsSerializedEvent, - SerializedAction as UiActionsSerializedAction, + createAction, DynamicActionManager, + IncompatibleActionError, + SerializedAction as UiActionsSerializedAction, + SerializedEvent as UiActionsSerializedEvent, } from './actions'; export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; export { diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 824be69a991a8..deacf61af41a8 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -273,14 +273,20 @@ export class UiActionsService { * Register an action factory. Action factories are used to configure and * serialize/deserialize dynamic actions. */ - public readonly registerActionFactory = (definition: ActionFactoryDefinition) => { + public readonly registerActionFactory = < + Config extends object = object, + FactoryContext extends object = object, + ActionContext extends object = object + >( + definition: ActionFactoryDefinition + ) => { if (this.actionFactories.has(definition.id)) { throw new Error(`ActionFactory [actionFactory.id = ${definition.id}] already registered.`); } - const actionFactory = new ActionFactory(definition); + const actionFactory = new ActionFactory(definition); - this.actionFactories.set(actionFactory.id, actionFactory); + this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory); }; public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index f6fcbba7f4a9a..783b0e1d4aff7 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -172,21 +172,23 @@ const ActionFactorySelector: React.FC = ({ } return ( - + {[...actionFactories] .sort((f1, f2) => f1.order - f2.order) .map(actionFactory => ( - onActionFactorySelected(actionFactory)} - > - {actionFactory.getIconType(context) && ( - - )} - + + onActionFactorySelected(actionFactory)} + > + {actionFactory.getIconType(context) && ( + + )} + + ))} ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index a6be110c44a02..9ef9800ab90e6 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -9,15 +9,11 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; -import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; +import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; -export interface FlyoutCreateDrilldownActionContext { - embeddable: IEmbeddable; -} - export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; drilldowns: () => Promise; @@ -40,14 +36,23 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; } - public async execute(context: FlyoutCreateDrilldownActionContext) { + public async isCompatible(context: EmbeddableContext) { + const isEditMode = context.embeddable.getInput().viewMode === 'edit'; + return isEditMode && this.isEmbeddableCompatible(context); + } + + public async execute(context: EmbeddableContext) { const overlays = await this.params.overlays(); const drilldowns = await this.params.drilldowns(); const dynamicActionManager = context.embeddable.dynamicActions; + if (!dynamicActionManager) { throw new Error(`Can't execute FlyoutCreateDrilldownAction without dynamicActionsManager`); } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index ed0cb425ee106..6695811500e73 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -12,7 +12,6 @@ import { } from '../../../../../../src/plugins/embeddable/public'; import { FlyoutCreateDrilldownAction, - FlyoutCreateDrilldownActionContext, FlyoutEditDrilldownAction, OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, @@ -22,7 +21,7 @@ import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldow declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { - [OPEN_FLYOUT_ADD_DRILLDOWN]: FlyoutCreateDrilldownActionContext; + [OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext; [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index 211a16451b30c..e80f4a24a56de 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -13,11 +13,6 @@ import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { DrilldownsDrilldown as Drilldown } from '../../../../../drilldowns/public'; import { txtGoToDashboard } from './i18n'; -export const dashboards = [ - { id: 'dashboard1', title: 'Dashboard 1' }, - { id: 'dashboard2', title: 'Dashboard 2' }, -]; - export interface Params { savedObjects: () => Promise; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts index 92f5a9be648bc..74be9c328f7f2 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts @@ -4,10 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EmbeddableVisTriggerContext } from '../../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableVisTriggerContext, + EmbeddableContext, +} from '../../../../../../../src/plugins/embeddable/public'; import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; -export type FactoryContext = any; +export type FactoryContext = EmbeddableContext; export type ActionContext = EmbeddableVisTriggerContext; export interface Config { diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index e258319a16b70..3f89a9f5d6441 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -6,14 +6,8 @@ import { CoreSetup } from 'src/core/public'; import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; -import { AnyDrilldown } from '../types'; - -// TODO: MOCK DATA -import { - // dashboardDrilldownActionFactory, - urlDrilldownActionFactory, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../advanced_ui_actions/public/components/action_wizard/test_data'; +import { Drilldown, DrilldownFactoryContext } from '../types'; +import { UiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../../../../src/plugins/ui_actions/public'; export interface DrilldownServiceSetupDeps { advancedUiActions: AdvancedUiActionsSetup; @@ -23,7 +17,13 @@ export interface DrilldownServiceSetupContract { /** * Convenience method to register a drilldown. */ - registerDrilldown: (drilldown: AnyDrilldown) => void; + registerDrilldown: < + Config extends object = object, + CreationContext extends object = object, + ExecutionContext extends object = object + >( + drilldown: Drilldown + ) => void; } export class DrilldownService { @@ -31,8 +31,12 @@ export class DrilldownService { core: CoreSetup, { advancedUiActions }: DrilldownServiceSetupDeps ): DrilldownServiceSetupContract { - const registerDrilldown: DrilldownServiceSetupContract['registerDrilldown'] = ({ - id, + const registerDrilldown = < + Config extends object = object, + CreationContext extends object = object, + ExecutionContext extends object = object + >({ + id: factoryId, places, CollectConfig, createConfig, @@ -40,35 +44,33 @@ export class DrilldownService { getDisplayName, euiIcon, execute, - }) => { - advancedUiActions.registerActionFactory({ - id, + }: Drilldown) => { + const actionFactory: ActionFactoryDefinition< + Config, + DrilldownFactoryContext, + ExecutionContext + > = { + id: factoryId, CollectConfig, createConfig, isConfigValid, getDisplayName, getIconType: () => euiIcon, isCompatible: async ({ place }: any) => (!places ? true : places.indexOf(place) > -1), - create: config => ({ + create: serializedAction => ({ id: '', - type: id as any, + type: factoryId, getIconType: () => euiIcon, - execute: async context => await execute(config, context), + execute: async context => await execute(serializedAction.config, context), }), - }); - }; + } as ActionFactoryDefinition< + Config, + DrilldownFactoryContext, + ExecutionContext + >; - /* - registerDrilldown({ - ...dashboardDrilldownActionFactory, - execute: () => alert('Dashboard drilldown!'), - } as any); - */ - registerDrilldown({ - ...urlDrilldownActionFactory, - euiIcon: 'link', - execute: () => alert('URL drilldown!'), - } as any); + advancedUiActions.registerActionFactory(actionFactory); + }; return { registerDrilldown, diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 4c5cfa2e596aa..21e28d8a1e64f 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -8,27 +8,93 @@ import { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } f export interface Drilldown< Config extends object = object, - FactoryContext extends object = object, + CreationContext extends object = object, ExecutionContext extends object = object -> - extends Pick< - ActionFactoryDefinition, - 'id' | 'createConfig' | 'CollectConfig' | 'isConfigValid' | 'getDisplayName' - > { +> { /** - * List of places where this drilldown should be available, e.g "dashboard". + * Globally unique identifier for this drilldown. + */ + id: string; + + /** + * List of places where this drilldown should be available, e.g "dashboard", "graph". * If omitted, the drilldown will be shown in all places. */ places?: string[]; /** - * Name of EUI icon to display next to this drilldown. + * Function that returns default config for this drilldown. + */ + createConfig: ActionFactoryDefinition< + Config, + DrilldownFactoryContext, + ExecutionContext + >['createConfig']; + + /** + * `UiComponent` that collections config for this drilldown. You can create + * a React component and transform it `UiComponent` using `uiToReactComponent` + * helper from `kibana_utils` plugin. + * + * ```tsx + * import React from 'react'; + * import { uiToReactComponent } from 'src/plugins/kibana_utils'; + * import { UiActionsCollectConfigProps as CollectConfigProps } from 'src/plugins/ui_actions/public'; + * + * type Props = CollectConfigProps; + * + * const ReactCollectConfig: React.FC = () => { + * return
Collecting config...'
; + * }; + * + * export const CollectConfig = uiToReactComponent(ReactCollectConfig); + * ``` + */ + CollectConfig: ActionFactoryDefinition< + Config, + DrilldownFactoryContext, + ExecutionContext + >['CollectConfig']; + + /** + * A validator function for the config object. Should always return a boolean + * given any input. + */ + isConfigValid: ActionFactoryDefinition< + Config, + DrilldownFactoryContext, + ExecutionContext + >['isConfigValid']; + + /** + * Name of EUI icon to display when showing this drilldown to user. */ euiIcon?: string; /** - * Implements the "navigation" action when user clicks something in the UI and - * instance of this drilldown is triggered. + * Should return an internationalized name of the drilldown, which will be + * displayed to the user. + */ + getDisplayName: () => string; + + /** + * Whether this drilldown should be considered for execution given `config` + * and `context`. When multiple drilldowns are attached to the same trigger + * user is presented with a context menu to pick one drilldown for execute. If + * this method returns `true` this trigger will appear in the context menu + * list, if `false`, it will not be presented to the user. If `doExecute` is + * not implemented, this drilldown will always be show to the user. + * + * @param config Config object that user configured this drilldown with. + * @param context Object that represents context in which the underlying + * `UIAction` of this drilldown is being executed in. + */ + doExecute?(config: Config, context: ExecutionContext): Promise; + + /** + * Implements the "navigation" action of the drilldown. This happens when + * user clicks something in the UI that executes a trigger to which this + * drilldown was attached. * * @param config Config object that user configured this drilldown with. * @param context Object that represents context in which the underlying @@ -37,4 +103,25 @@ export interface Drilldown< execute(config: Config, context: ExecutionContext): void; } -export type AnyDrilldown = Drilldown; +/** + * Context object used when creating a drilldown. + */ +export interface DrilldownFactoryContext { + /** + * List of places as configured in @type {Drilldown} interface. + */ + places?: string[]; + + /** + * Context provided to the drilldown factory by the place where the UI is + * rendered. For example, for the "dashboard" place, this context contains + * the ID of the current dashboard, which could be used for filtering it out + * of the list. + */ + placeContext: T; + + /** + * List of triggers that user selected in the UI. + */ + triggers: string[]; +} From 537f8ee80c9033587ca14f073b2f10173642a632 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 16 Mar 2020 15:31:32 +0100 Subject: [PATCH 35/79] workaround issue with closing flyout when navigating away Adds overlay just like other flyouts which makes this defect harder to bump in --- .../public/components/flyout_frame/flyout_frame.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index fc803af16a266..199bf57cd59d2 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiButtonEmpty, EuiButtonIcon, + EuiFlyout, } from '@elastic/eui'; import { txtClose, txtBack } from './i18n'; @@ -25,6 +26,8 @@ export interface FlyoutFrameProps { onBack?: () => void; } +const noop = () => {}; + /** * @todo This component can be moved to `kibana_react`. */ @@ -83,10 +86,10 @@ export const FlyoutFrame: React.FC = ({ ); return ( - <> + {headerFragment} {children} {footerFragment} - + ); }; From 0ad89336fd849e6f2865587c26cbf096b1e43937 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 16 Mar 2020 15:34:44 +0100 Subject: [PATCH 36/79] fix react key issue in action_wizard --- .../public/components/action_wizard/action_wizard.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 783b0e1d4aff7..1716a7afa8741 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -172,14 +172,13 @@ const ActionFactorySelector: React.FC = ({ } return ( - + {[...actionFactories] .sort((f1, f2) => f1.order - f2.order) .map(actionFactory => ( - + onActionFactorySelected(actionFactory)} From 94894c10e264173d26a03ab8f83a3f091c3c21c5 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 16 Mar 2020 16:55:38 +0100 Subject: [PATCH 37/79] =?UTF-8?q?don=E2=80=99t=20open=202=20flyouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/public/overlays/flyout/flyout_service.tsx | 1 + .../drilldowns/actions/flyout_create_drilldown/index.tsx | 5 ++++- .../drilldowns/actions/flyout_edit_drilldown/index.tsx | 5 ++++- .../public/components/flyout_frame/flyout_frame.tsx | 7 ++----- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/core/public/overlays/flyout/flyout_service.tsx b/src/core/public/overlays/flyout/flyout_service.tsx index b609b2ce1d741..444430175d4f2 100644 --- a/src/core/public/overlays/flyout/flyout_service.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -91,6 +91,7 @@ export interface OverlayFlyoutStart { export interface OverlayFlyoutOpenOptions { className?: string; closeButtonAriaLabel?: string; + ownFocus?: boolean; 'data-test-subj'?: string; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index 9ef9800ab90e6..c4c774e2caf96 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -65,7 +65,10 @@ export class FlyoutCreateDrilldownAction implements ActionByType - ) + ), + { + ownFocus: true, + } ); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index 6e94c8ac202ad..b658d44e8799a 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -63,7 +63,10 @@ export class FlyoutEditDrilldownAction implements ActionByType - ) + ), + { + ownFocus: true, + } ); } } diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index 199bf57cd59d2..fc803af16a266 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -14,7 +14,6 @@ import { EuiFlexItem, EuiButtonEmpty, EuiButtonIcon, - EuiFlyout, } from '@elastic/eui'; import { txtClose, txtBack } from './i18n'; @@ -26,8 +25,6 @@ export interface FlyoutFrameProps { onBack?: () => void; } -const noop = () => {}; - /** * @todo This component can be moved to `kibana_react`. */ @@ -86,10 +83,10 @@ export const FlyoutFrame: React.FC = ({ ); return ( - + <> {headerFragment} {children} {footerFragment} - + ); }; From 65f541453185a7f6264b866962712b4373029fa7 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 16 Mar 2020 18:03:51 +0100 Subject: [PATCH 38/79] fix action order https://github.com/elastic/kibana/issues/60138 --- .../public/actions/replace_panel_action.tsx | 2 +- src/plugins/embeddable/public/bootstrap.ts | 4 --- src/plugins/embeddable/public/index.ts | 1 - .../public/lib/actions/edit_panel_action.ts | 2 +- .../public/lib/embeddables/embeddable.tsx | 9 +++--- .../public/lib/panel/embeddable_panel.tsx | 28 +++---------------- .../customize_title/customize_panel_action.ts | 8 ++---- .../panel_actions/inspect_panel_action.ts | 2 +- .../panel_actions/remove_panel_action.ts | 2 +- .../public/lib/triggers/triggers.ts | 7 ----- .../build_eui_context_menu_panels.tsx | 27 +++++------------- .../ui_actions/public/context_menu/index.ts | 5 +--- src/plugins/ui_actions/public/index.ts | 2 +- .../public/custom_time_range_action.tsx | 2 +- .../actions/flyout_create_drilldown/index.tsx | 2 +- .../actions/flyout_edit_drilldown/index.tsx | 2 +- .../dashboard_drilldowns_services.ts | 6 ++-- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 19 files changed, 30 insertions(+), 83 deletions(-) diff --git a/src/plugins/dashboard/public/actions/replace_panel_action.tsx b/src/plugins/dashboard/public/actions/replace_panel_action.tsx index 26d9c5c8ad4dd..2a59907133340 100644 --- a/src/plugins/dashboard/public/actions/replace_panel_action.tsx +++ b/src/plugins/dashboard/public/actions/replace_panel_action.tsx @@ -37,7 +37,7 @@ export interface ReplacePanelActionContext { export class ReplacePanelAction implements ActionByType { public readonly type = ACTION_REPLACE_PANEL; public readonly id = ACTION_REPLACE_PANEL; - public order = 11; + public order = 3; constructor( private core: CoreStart, diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 205fd5be8d9ff..c8c4f0b95c458 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -19,12 +19,10 @@ import { UiActionsSetup } from '../../ui_actions/public'; import { contextMenuTrigger, - contextMenuDrilldownsTrigger, createFilterAction, panelBadgeTrigger, EmbeddableContext, CONTEXT_MENU_TRIGGER, - CONTEXT_MENU_DRILLDOWNS_TRIGGER, PANEL_BADGE_TRIGGER, ACTION_ADD_PANEL, ACTION_CUSTOMIZE_PANEL, @@ -38,7 +36,6 @@ import { declare module '../../ui_actions/public' { export interface TriggerContextMapping { [CONTEXT_MENU_TRIGGER]: EmbeddableContext; - [CONTEXT_MENU_DRILLDOWNS_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; } @@ -58,7 +55,6 @@ declare module '../../ui_actions/public' { */ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); - uiActions.registerTrigger(contextMenuDrilldownsTrigger); uiActions.registerTrigger(panelBadgeTrigger); const actionApplyFilter = createFilterAction(); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 90ab0c0d735b3..1474f9ed63052 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -31,7 +31,6 @@ export { ContainerInput, ContainerOutput, CONTEXT_MENU_TRIGGER, - CONTEXT_MENU_DRILLDOWNS_TRIGGER, contextMenuTrigger, ACTION_EDIT_PANEL, EditPanelAction, diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 82f8e33b7ae2f..ebf5dedb3f513 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -32,7 +32,7 @@ interface ActionContext { export class EditPanelAction implements Action { public readonly type = ACTION_EDIT_PANEL; public readonly id = ACTION_EDIT_PANEL; - public order = 15; + public order = 50; constructor(private readonly getEmbeddableFactory: GetEmbeddableFactory) {} diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 0bb78ff2fdc31..12b980638a6ca 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -16,16 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { isEqual, cloneDeep } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; import * as Rx from 'rxjs'; -import { Adapters } from '../types'; +import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; -import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; -import { ViewMode } from '../types'; +import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { EmbeddableActionStorage } from './embeddable_action_storage'; import { - UiActionsStart, UiActionsDynamicActionManager, + UiActionsStart, } from '../../../../../plugins/ui_actions/public'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 91846e45525fc..8039ec9534d22 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -20,22 +20,12 @@ import { EuiContextMenuPanelDescriptor, EuiPanel, htmlIdGenerator } from '@elast import classNames from 'classnames'; import React from 'react'; import { Subscription } from 'rxjs'; -import { - buildContextMenuForActions, - UiActionsService, - Action, - contextMenuSeparatorAction, -} from '../ui_actions'; +import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions'; import { CoreStart, OverlayStart } from '../../../../../core/public'; import { toMountPoint } from '../../../../kibana_react/public'; import { Start as InspectorStartContract } from '../inspector'; -import { - CONTEXT_MENU_TRIGGER, - CONTEXT_MENU_DRILLDOWNS_TRIGGER, - PANEL_BADGE_TRIGGER, - EmbeddableContext, -} from '../triggers'; +import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, EmbeddableContext } from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../types'; @@ -236,15 +226,11 @@ export class EmbeddablePanel extends React.Component { let regularActions = await this.props.getActions(CONTEXT_MENU_TRIGGER, { embeddable: this.props.embeddable, }); - let drilldownActions = await this.props.getActions(CONTEXT_MENU_DRILLDOWNS_TRIGGER, { - embeddable: this.props.embeddable, - }); const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { const removeDisabledActions = removeById(disabledActions); regularActions = regularActions.filter(removeDisabledActions); - drilldownActions = drilldownActions.filter(removeDisabledActions); } const createGetUserData = (overlays: OverlayStart) => @@ -283,16 +269,10 @@ export class EmbeddablePanel extends React.Component { new EditPanelAction(this.props.getEmbeddableFactory), ]; - const sortedRegularActions = [...regularActions, ...extraActions].sort(sortByOrderField); - const sortedDrilldownActions = [...drilldownActions].sort(sortByOrderField); - const actions = [ - ...sortedDrilldownActions, - ...(sortedDrilldownActions.length ? [contextMenuSeparatorAction] : []), - ...sortedRegularActions, - ]; + const sortedActions = [...regularActions, ...extraActions].sort(sortByOrderField); return await buildContextMenuForActions({ - actions, + actions: sortedActions, actionContext: { embeddable: this.props.embeddable }, closeMenu: this.closeMyContextMenuPanel, }); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts index c0e43c0538833..36957c3b79491 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.ts @@ -33,15 +33,13 @@ interface ActionContext { export class CustomizePanelTitleAction implements Action { public readonly type = ACTION_CUSTOMIZE_PANEL; public id = ACTION_CUSTOMIZE_PANEL; - public order = 10; + public order = 40; - constructor(private readonly getDataFromUser: GetUserData) { - this.order = 10; - } + constructor(private readonly getDataFromUser: GetUserData) {} public getDisplayName() { return i18n.translate('embeddableApi.customizePanel.action.displayName', { - defaultMessage: 'Customize panel', + defaultMessage: 'Edit panel title', }); } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts index d04f35715537c..ae9645767b267 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.ts @@ -31,7 +31,7 @@ interface ActionContext { export class InspectPanelAction implements Action { public readonly type = ACTION_INSPECT_PANEL; public readonly id = ACTION_INSPECT_PANEL; - public order = 10; + public order = 20; constructor(private readonly inspector: InspectorStartContract) {} diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts index ee7948f3d6a4a..a6d4128f3f106 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.ts @@ -41,7 +41,7 @@ function hasExpandedPanelInput( export class RemovePanelAction implements Action { public readonly type = REMOVE_PANEL_ACTION; public readonly id = REMOVE_PANEL_ACTION; - public order = 5; + public order = 1; constructor() {} diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index 37a7bb12a29cc..0052403816eb8 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -40,13 +40,6 @@ export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { description: 'Triggered on top-right corner context-menu select.', }; -export const CONTEXT_MENU_DRILLDOWNS_TRIGGER = 'CONTEXT_MENU_DRILLDOWNS_TRIGGER'; -export const contextMenuDrilldownsTrigger: Trigger<'CONTEXT_MENU_DRILLDOWNS_TRIGGER'> = { - id: CONTEXT_MENU_DRILLDOWNS_TRIGGER, - title: 'Drilldown context menu', - description: 'Triggered on top-right corner context-menu select.', -}; - export const PANEL_BADGE_TRIGGER = 'PANEL_BADGE_TRIGGER'; export const panelBadgeTrigger: Trigger<'PANEL_BADGE_TRIGGER'> = { id: PANEL_BADGE_TRIGGER, diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index e4c89b6a3bb8a..05c8c1c0f3089 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -18,22 +18,11 @@ */ import * as React from 'react'; -import { - EuiContextMenuPanelDescriptor, - EuiContextMenuPanelItemDescriptor, - EuiHorizontalRule, -} from '@elastic/eui'; +import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { uiToReactComponent, reactToUiComponent } from '../../../kibana_react/public'; -import { Action, ActionInternal } from '../actions'; - -export const contextMenuSeparatorAction = new ActionInternal({ - id: 'CONTEXT_MENU_SEPARATOR', - getDisplayName: () => 'separator', - MenuItem: reactToUiComponent(() => ), - execute: () => Promise.resolve(), -}); +import { uiToReactComponent } from '../../../kibana_react/public'; +import { Action } from '../actions'; /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. @@ -113,12 +102,10 @@ function convertPanelActionToContextMenuItem({ 'data-test-subj': `embeddablePanelAction-${action.id}`, }; - if (action.id !== 'CONTEXT_MENU_SEPARATOR') { - menuPanelItem.onClick = () => { - action.execute(actionContext); - closeMenu(); - }; - } + menuPanelItem.onClick = () => { + action.execute(actionContext); + closeMenu(); + }; if (action.getHref) { const href = action.getHref(actionContext); diff --git a/src/plugins/ui_actions/public/context_menu/index.ts b/src/plugins/ui_actions/public/context_menu/index.ts index 96df8f7601b0b..aa8df8b6965d8 100644 --- a/src/plugins/ui_actions/public/context_menu/index.ts +++ b/src/plugins/ui_actions/public/context_menu/index.ts @@ -17,8 +17,5 @@ * under the License. */ -export { - buildContextMenuForActions, - contextMenuSeparatorAction, -} from './build_eui_context_menu_panels'; +export { buildContextMenuForActions } from './build_eui_context_menu_panels'; export { openContextMenu } from './open_context_menu'; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index f674179a217aa..789604fcbcf1f 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -38,7 +38,7 @@ export { SerializedAction as UiActionsSerializedAction, SerializedEvent as UiActionsSerializedEvent, } from './actions'; -export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu'; +export { buildContextMenuForActions } from './context_menu'; export { Presentable as UiActionsPresentable, Configurable as UiActionsConfigurable, diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx index 325a5ddc10179..c0cd8d5540db2 100644 --- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.tsx @@ -44,7 +44,7 @@ export class CustomTimeRangeAction implements ActionByType { public readonly type = OPEN_FLYOUT_ADD_DRILLDOWN; public readonly id = OPEN_FLYOUT_ADD_DRILLDOWN; - public order = 2; + public order = 12; constructor(protected readonly params: OpenFlyoutAddDrilldownParams) {} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index b658d44e8799a..b8d20731eecd3 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -26,7 +26,7 @@ export interface FlyoutEditDrilldownParams { export class FlyoutEditDrilldownAction implements ActionByType { public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; - public order = 1; + public order = 10; constructor(protected readonly params: FlyoutEditDrilldownParams) {} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 6695811500e73..a8554d9e2339f 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -7,7 +7,7 @@ import { CoreSetup } from 'src/core/public'; import { SetupDependencies } from '../../plugin'; import { - CONTEXT_MENU_DRILLDOWNS_TRIGGER, + CONTEXT_MENU_TRIGGER, EmbeddableContext, } from '../../../../../../src/plugins/embeddable/public'; import { @@ -37,11 +37,11 @@ export class DashboardDrilldownsService { const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); plugins.uiActions.registerAction(actionFlyoutCreateDrilldown); - plugins.uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutCreateDrilldown); + plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); plugins.uiActions.registerAction(actionFlyoutEditDrilldown); - plugins.uiActions.attachAction(CONTEXT_MENU_DRILLDOWNS_TRIGGER, actionFlyoutEditDrilldown); + plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ savedObjects, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fb0eb6e4bf804..845cad8c83fee 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -631,7 +631,6 @@ "embeddableApi.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました", "embeddableApi.addPanel.Title": "パネルの追加", - "embeddableApi.customizePanel.action.displayName": "パネルをカスタマイズ", "embeddableApi.customizePanel.modal.cancel": "キャンセル", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleFormRowLabel": "パネルタイトル", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel": "パネルのカスタムタイトルを入力してください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0a9c82afaec1d..61d856d2a7a00 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -631,7 +631,6 @@ "embeddableApi.addPanel.noMatchingObjectsMessage": "未找到任何匹配对象。", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", "embeddableApi.addPanel.Title": "添加面板", - "embeddableApi.customizePanel.action.displayName": "定制面板", "embeddableApi.customizePanel.modal.cancel": "取消", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleFormRowLabel": "面板标题", "embeddableApi.customizePanel.modal.optionsMenuForm.panelTitleInputAriaLabel": "为面板输入定制标题", From a405f833c55e3f82d5c29abacde3d4be168afc04 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 17 Mar 2020 00:08:52 +0100 Subject: [PATCH 39/79] Drilldowns reload stored (#60336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: 💄 don't use double equals __ * feat: 🎸 add reload$ to ActionStorage interface * feat: 🎸 add reload$ to embeddable event storage * feat: 🎸 add storage syncing to DynamicActionManager * refactor: 💡 use state from DynamicActionManager in React * fix: 🐛 add check for manager being stopped --- .../public/lib/embeddables/embeddable.tsx | 21 ++++++-- .../embeddables/embeddable_action_storage.ts | 15 +++--- .../public/lib/embeddables/i_embeddable.ts | 2 +- .../public/actions/dynamic_action_manager.ts | 51 ++++++++++++++++++- .../public/actions/dynamic_action_storage.ts | 22 +++++++- src/plugins/ui_actions/public/index.ts | 2 + ...onnected_flyout_manage_drilldowns.test.tsx | 13 +---- .../connected_flyout_manage_drilldowns.tsx | 33 +----------- .../test_data.ts | 46 +++++++++++++---- 9 files changed, 138 insertions(+), 67 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 12b980638a6ca..d272e8a3d922d 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -58,21 +58,25 @@ export abstract class Embeddable< // to update input when the parent changes. private parentSubscription?: Rx.Subscription; + private storageSubscription?: Rx.Subscription; + // TODO: Rename to destroyed. private destoyed: boolean = false; - private __dynamicActions?: UiActionsDynamicActionManager; + private storage = new EmbeddableActionStorage(this); + + private cachedDynamicActions?: UiActionsDynamicActionManager; public get dynamicActions(): UiActionsDynamicActionManager | undefined { if (!this.params.uiActions) return undefined; - if (!this.__dynamicActions) { - this.__dynamicActions = new UiActionsDynamicActionManager({ + if (!this.cachedDynamicActions) { + this.cachedDynamicActions = new UiActionsDynamicActionManager({ isCompatible: async ({ embeddable }: any) => embeddable.runtimeId === this.runtimeId, - storage: new EmbeddableActionStorage(this), + storage: this.storage, uiActions: this.params.uiActions, }); } - return this.__dynamicActions; + return this.cachedDynamicActions; } constructor( @@ -112,6 +116,9 @@ export abstract class Embeddable< console.error(error); /* eslint-enable */ }); + this.storageSubscription = this.input$.subscribe(() => { + this.storage.reload$.next(); + }); } } @@ -201,6 +208,10 @@ export abstract class Embeddable< }); } + if (this.storageSubscription) { + this.storageSubscription.unsubscribe(); + } + if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts index dc8466607a984..b224a67144df4 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -17,11 +17,16 @@ * under the License. */ -import { UiActionsActionStorage, UiActionsSerializedEvent } from '../../../../ui_actions/public'; +import { + UiActionsAbstractActionStorage, + UiActionsSerializedEvent, +} from '../../../../ui_actions/public'; import { Embeddable } from '..'; -export class EmbeddableActionStorage implements UiActionsActionStorage { - constructor(private readonly embbeddable: Embeddable) {} +export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { + constructor(private readonly embbeddable: Embeddable) { + super(); + } async create(event: UiActionsSerializedEvent) { const input = this.embbeddable.getInput(); @@ -96,10 +101,6 @@ export class EmbeddableActionStorage implements UiActionsActionStorage { return (input.events || []) as UiActionsSerializedEvent[]; } - async count(): Promise { - return this.__list().length; - } - async list(): Promise { return this.__list(); } diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 58fc7327232b8..69b7792be9eb6 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -34,7 +34,7 @@ export interface EmbeddableInput { /** * Reserved key for `ui_actions` events. */ - events?: unknown; + events?: Array<{ eventId: string }>; /** * List of action IDs that this embeddable should not render. diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index ae6c97fcc78df..a40abb271d85a 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -18,6 +18,7 @@ */ import { v4 as uuidv4 } from 'uuid'; +import { Subscription } from 'rxjs'; import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; import { UiActionsService } from '../service'; import { SerializedAction } from './types'; @@ -25,6 +26,17 @@ import { ActionDefinition } from './action'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; import { StateContainer, createStateContainer } from '../../../kibana_utils'; +const compareEvents = ( + a: ReadonlyArray<{ eventId: string }>, + b: ReadonlyArray<{ eventId: string }> +) => { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) if (a[i].eventId !== b[i].eventId) return false; + return true; +}; + +export type DynamicActionManagerState = State; + export interface DynamicActionManagerParams { storage: ActionStorage; uiActions: Pick< @@ -38,8 +50,8 @@ export class DynamicActionManager { static idPrefixCounter = 0; private readonly idPrefix = `D_ACTION_${DynamicActionManager.idPrefixCounter++}_`; - private stopped: boolean = false; + private reloadSubscription?: Subscription; /** * UI State of the dynamic action manager. @@ -85,6 +97,35 @@ export class DynamicActionManager { uiActions.removeTriggerAction(triggerId as any, actionId); } + private syncId = 0; + + /** + * This function is called every time stored events might have changed not by + * us. For example, when in edit mode on dashboard user presses "back" button + * in the browser, then contents of storage changes. + */ + private onSync = () => { + if (this.stopped) return; + + (async () => { + const syncId = ++this.syncId; + const events = await this.params.storage.list(); + + if (this.stopped) return; + if (syncId !== this.syncId) return; + if (compareEvents(events, this.ui.get().events)) return; + + for (const event of this.ui.get().events) this.killAction(event); + for (const event of events) this.reviveAction(event); + this.ui.transitions.finishFetching(events); + })().catch(error => { + /* eslint-disable */ + console.log('Dynamic action manager storage reload failed.'); + console.error(error); + /* eslint-enable */ + }); + }; + // Public API: --------------------------------------------------------------- /** @@ -108,6 +149,10 @@ export class DynamicActionManager { const events = await this.params.storage.list(); for (const event of events) this.reviveAction(event); this.ui.transitions.finishFetching(events); + + if (this.params.storage.reload$) { + this.reloadSubscription = this.params.storage.reload$.subscribe(this.onSync); + } } /** @@ -121,6 +166,10 @@ export class DynamicActionManager { for (const event of events) { this.killAction(event); } + + if (this.reloadSubscription) { + this.reloadSubscription.unsubscribe(); + } } /** diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts index a92909261da32..820cc59f77028 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts @@ -17,6 +17,7 @@ * under the License. */ +import { Observable, Subject } from 'rxjs'; import { SerializedAction } from './types'; /** @@ -29,7 +30,7 @@ export interface SerializedEvent { } /** - * This interface needs to be implemented by dynamic action users if they + * This CRUD interface needs to be implemented by dynamic action users if they * want to persist the dynamic actions. It has a default implementation in * Embeddables, however one can use the dynamic actions without Embeddables, * in that case they have to implement this interface. @@ -41,4 +42,23 @@ export interface ActionStorage { read(eventId: string): Promise; count(): Promise; list(): Promise; + + /** + * Triggered every time events changed in storage and should be re-loaded. + */ + readonly reload$?: Observable; +} + +export abstract class AbstractActionStorage implements ActionStorage { + public readonly reload$: Observable & Pick, 'next'> = new Subject(); + + public async count(): Promise { + return (await this.list()).length; + } + + abstract create(event: SerializedEvent): Promise; + abstract update(event: SerializedEvent): Promise; + abstract remove(eventId: string): Promise; + abstract read(eventId: string): Promise; + abstract list(): Promise; } diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 789604fcbcf1f..9265d35bad9a9 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -32,8 +32,10 @@ export { ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, ActionStorage as UiActionsActionStorage, + AbstractActionStorage as UiActionsAbstractActionStorage, createAction, DynamicActionManager, + DynamicActionManagerState, IncompatibleActionError, SerializedAction as UiActionsSerializedAction, SerializedEvent as UiActionsSerializedEvent, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index 7d6b7559ec936..c115c18a4f3e8 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -173,18 +173,7 @@ test('Create only mode', async () => { expect(await mockDynamicActionManager.count()).toBe(1); }); -test("Error when can't fetch drilldown list", async () => { - const error = new Error('Oops'); - jest.spyOn(mockDynamicActionManager, 'list').mockImplementationOnce(async () => { - throw error; - }); - render(); - await wait(() => - expect(notifications.toasts.addError).toBeCalledWith(error, { - title: toastDrilldownsFetchError, - }) - ); -}); +test.todo("Error when can't fetch drilldown list"); test("Error when can't save drilldown changes", async () => { const error = new Error('Oops'); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 9462b89fc26ff..ee1afbeb2ed0c 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -5,7 +5,6 @@ */ import React, { useEffect, useState } from 'react'; -import useMount from 'react-use/lib/useMount'; import useMountedState from 'react-use/lib/useMountedState'; import { AdvancedUiActionsActionFactory as ActionFactory, @@ -20,6 +19,7 @@ import { UiActionsSerializedEvent, UiActionsSerializedAction, } from '../../../../../../src/plugins/ui_actions/public'; +import { useContainerState } from '../../../../../../src/plugins/kibana_utils/common'; import { DrilldownListItem } from '../list_manage_drilldowns'; import { toastDrilldownCreated, @@ -27,7 +27,6 @@ import { toastDrilldownEdited, toastDrilldownsCRUDError, toastDrilldownsDeleted, - toastDrilldownsFetchError, } from './i18n'; interface ConnectedFlyoutManageDrilldownsProps { @@ -243,8 +242,8 @@ function useDrilldownsStateManager( actionManager: DynamicActionManager, notifications: NotificationsStart ) { + const { events: drilldowns } = useContainerState(actionManager.state); const [isLoading, setIsLoading] = useState(false); - const [drilldowns, setDrilldowns] = useState(); const isMounted = useMountedState(); async function run(op: () => Promise) { @@ -259,36 +258,8 @@ function useDrilldownsStateManager( setIsLoading(false); return; } - - await reload(); } - async function reload() { - if (!isMounted) { - // don't do any side effects anymore because component is already unmounted - return; - } - if (!isLoading) { - setIsLoading(true); - } - try { - const drilldownsList = await actionManager.list(); - if (!isMounted) { - return; - } - setDrilldowns(drilldownsList); - setIsLoading(false); - } catch (e) { - notifications.toasts.addError(e, { - title: toastDrilldownsFetchError, - }); - } - } - - useMount(() => { - reload(); - }); - async function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { await run(async () => { await actionManager.createEvent(action, triggerId); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index aaf9a0e3dd020..a024c4ad38748 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -7,46 +7,74 @@ import uuid from 'uuid'; import { DynamicActionManager, + DynamicActionManagerState, UiActionsSerializedAction, - UiActionsSerializedEvent, } from '../../../../../../src/plugins/ui_actions/public'; +import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common'; class MockDynamicActionManager implements PublicMethodsOf { - private readonly events: UiActionsSerializedEvent[] = []; + public readonly state = createStateContainer({ + isFetchingEvents: false, + fetchCount: 0, + events: [], + }); async count() { - return this.events.length; + return this.state.get().events.length; } + async list() { - return this.events; + return this.state.get().events; } + async createEvent( action: UiActionsSerializedAction, triggerId: string = 'VALUE_CLICK_TRIGGER' ) { - this.events.push({ + const event = { action, triggerId, eventId: uuid(), + }; + const state = this.state.get(); + this.state.set({ + ...state, + events: [...state.events, event], }); } + async deleteEvents(eventIds: string[]) { + const state = this.state.get(); + let events = state.events; + eventIds.forEach(id => { - const idx = this.events.findIndex(e => e.eventId === id); - this.events.splice(idx, 1); + events = events.filter(e => e.eventId !== id); + }); + + this.state.set({ + ...state, + events, }); } + async updateEvent( eventId: string, action: UiActionsSerializedAction, triggerId: string = 'VALUE_CLICK_TRIGGER' ) { - const idx = this.events.findIndex(e => e.eventId === eventId); - this.events[idx] = { + const state = this.state.get(); + const events = state.events; + const idx = events.findIndex(e => e.eventId === eventId); + const event = { eventId, action, triggerId, }; + + this.state.set({ + ...state, + events: [...events.slice(0, idx), event, ...events.slice(idx + 1)], + }); } async deleteEvent() { From cfdab2260751b774825caf22072fe7dea54b2791 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 17 Mar 2020 00:58:05 +0100 Subject: [PATCH 40/79] Drilldowns triggers (#60339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 make use of supportedTriggers() * feat: 🎸 pass in context to configuration component * feat: 🎸 augment factory context --- .../ui_actions/public/actions/action_factory.ts | 2 +- .../ui_actions/public/util/configurable.ts | 11 ++++++++--- .../components/action_wizard/action_wizard.tsx | 9 +++++---- .../actions/flyout_create_drilldown/index.tsx | 2 +- .../connected_flyout_manage_drilldowns.test.tsx | 2 +- .../connected_flyout_manage_drilldowns.tsx | 15 +++++++++++---- x-pack/plugins/drilldowns/public/types.ts | 4 ++-- 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/src/plugins/ui_actions/public/actions/action_factory.ts index ae20e8b924b19..bc0ec844d00f5 100644 --- a/src/plugins/ui_actions/public/actions/action_factory.ts +++ b/src/plugins/ui_actions/public/actions/action_factory.ts @@ -28,7 +28,7 @@ export class ActionFactory< Config extends object = object, FactoryContext extends object = object, ActionContext extends object = object -> implements Presentable, Configurable { +> implements Presentable, Configurable { constructor( protected readonly def: ActionFactoryDefinition ) {} diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index 7d2ceb61c106e..fe235d4b5daac 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -22,7 +22,7 @@ import { UiComponent } from 'src/plugins/kibana_utils/common'; /** * Represents something that can be configured by user using UI. */ -export interface Configurable { +export interface Configurable { /** * Create default config for this item, used when item is created for the first time. */ @@ -36,13 +36,13 @@ export interface Configurable { /** * `UiComponent` to be rendered when collecting configuration for this item. */ - readonly CollectConfig: UiComponent>; + readonly CollectConfig: UiComponent>; } /** * Props provided to `CollectConfig` component on every re-render. */ -export interface CollectConfigProps { +export interface CollectConfigProps { /** * Current (latest) config of the item. */ @@ -52,4 +52,9 @@ export interface CollectConfigProps { * Callback called when user updates the config in UI. */ onConfig: (config: Config) => void; + + /** + * Context information about where component is being rendered. + */ + context: Context; } diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 1716a7afa8741..466f22d785685 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -143,10 +143,11 @@ const SelectedActionFactory: React.FC = ({
- {actionFactory.ReactCollectConfig({ - config, - onConfig: onConfigChange, - })} +
); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx index e180753b51936..3b4f536006cde 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx @@ -38,7 +38,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; } diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index c115c18a4f3e8..60825f893903e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -19,7 +19,7 @@ import { TEST_SUBJ_DRILLDOWN_ITEM } from '../list_manage_drilldowns'; import { WELCOME_MESSAGE_TEST_SUBJ } from '../drilldown_hello_bar'; import { coreMock } from '../../../../../../src/core/public/mocks'; import { NotificationsStart } from 'kibana/public'; -import { toastDrilldownsCRUDError, toastDrilldownsFetchError } from './i18n'; +import { toastDrilldownsCRUDError } from './i18n'; const storage = new Storage(new StubBrowserStorage()); const notifications = coreMock.createStart().notifications; diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index ee1afbeb2ed0c..a0f18589bc238 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -28,6 +28,7 @@ import { toastDrilldownsCRUDError, toastDrilldownsDeleted, } from './i18n'; +import { DrilldownFactoryContext } from '../../types'; interface ConnectedFlyoutManageDrilldownsProps { context: Context; @@ -65,9 +66,15 @@ export function createFlyoutManageDrilldowns({ return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; + const factoryContext: DrilldownFactoryContext = { + place: '', + placeContext: props.context, + triggers: [], + }; + const actionFactories = useCompatibleActionFactoriesForCurrentContext( allActionFactories, - props.context + factoryContext ); const [route, setRoute] = useState( @@ -121,8 +128,8 @@ export function createFlyoutManageDrilldowns({ return { id: drilldown.eventId, drilldownName: drilldown.action.name, - actionName: actionFactory?.getDisplayName(props.context) ?? drilldown.action.factoryId, - icon: actionFactory?.getIconType(props.context), + actionName: actionFactory?.getDisplayName(factoryContext) ?? drilldown.action.factoryId, + icon: actionFactory?.getIconType(factoryContext), }; } @@ -168,7 +175,7 @@ export function createFlyoutManageDrilldowns({ setRoute(Routes.Manage); setCurrentEditId(null); }} - actionFactoryContext={props.context} + actionFactoryContext={factoryContext} initialDrilldownWizardConfig={resolveInitialDrilldownWizardConfig()} /> ); diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 21e28d8a1e64f..f34aa89ddd211 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -108,9 +108,9 @@ export interface Drilldown< */ export interface DrilldownFactoryContext { /** - * List of places as configured in @type {Drilldown} interface. + * Place where factory is being rendered. */ - places?: string[]; + place?: string; /** * Context provided to the drilldown factory by the place where the UI is From 62edd236d734036beea600884b90dc1d4591f74c Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 17 Mar 2020 09:57:21 +0100 Subject: [PATCH 41/79] =?UTF-8?q?fix:=20=F0=9F=90=9B=20stop=20infinite=20r?= =?UTF-8?q?e-rendering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/lib/panel/embeddable_panel.test.tsx | 2 +- .../connected_flyout_manage_drilldowns.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 17541b4e5f9b8..83d3d5e10761b 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -244,7 +244,7 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { const fooContextMenuActionItem1 = findTestSubject(component1, 'embeddablePanelAction-FOO'); const fooContextMenuActionItem2 = findTestSubject(component2, 'embeddablePanelAction-FOO'); - expect(fooContextMenuActionItem1.length).toBe(2); + expect(fooContextMenuActionItem1.length).toBe(1); expect(fooContextMenuActionItem2.length).toBe(0); }); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index a0f18589bc238..137be465fcd87 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -66,11 +66,14 @@ export function createFlyoutManageDrilldowns({ return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; - const factoryContext: DrilldownFactoryContext = { - place: '', - placeContext: props.context, - triggers: [], - }; + const factoryContext: DrilldownFactoryContext = React.useMemo( + () => ({ + place: '', + placeContext: props.context, + triggers: [], + }), + [props.context] + ); const actionFactories = useCompatibleActionFactoriesForCurrentContext( allActionFactories, From 7a7beeb333c86404f66e0971ea71f82a245faf90 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 17 Mar 2020 11:16:34 +0100 Subject: [PATCH 42/79] Drilldowns multitrigger (#60357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add support for multiple triggers * feat: 🎸 enable Drilldowns for TSVB Although TSVB brushing event is now broken on master, KibanaApp plans to fix it in 7.7 --- .../public/embeddable/visualize_embeddable.ts | 2 +- .../public/actions/dynamic_action_manager.ts | 31 +++++++----- .../public/actions/dynamic_action_storage.ts | 2 +- src/plugins/ui_actions/public/mocks.ts | 2 + .../public/service/ui_actions_service.ts | 4 +- ...onnected_flyout_manage_drilldowns.test.tsx | 2 +- .../connected_flyout_manage_drilldowns.tsx | 47 +++++++++++++------ .../test_data.ts | 9 ++-- 8 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 7f2ad2de1c4a8..1567f904e951a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -406,6 +406,7 @@ export class VisualizeEmbeddable extends Embeddable; isCompatible: (context: C) => Promise; } @@ -75,7 +76,7 @@ export class DynamicActionManager { } protected reviveAction(event: SerializedEvent) { - const { eventId, triggerId, action } = event; + const { eventId, triggers, action } = event; const { uiActions, isCompatible } = this.params; const { name } = action; @@ -88,13 +89,16 @@ export class DynamicActionManager { getDisplayName: () => name, }; - uiActions.addTriggerAction(triggerId as any, actionDefinition); + uiActions.registerAction(actionDefinition); + for (const trigger of triggers) uiActions.__attachAction(trigger as any, actionId); } - protected killAction({ eventId, triggerId }: SerializedEvent) { + protected killAction({ eventId, triggers }: SerializedEvent) { const { uiActions } = this.params; const actionId = this.generateActionId(eventId); - uiActions.removeTriggerAction(triggerId as any, actionId); + + for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId); + uiActions.unregisterAction(actionId); } private syncId = 0; @@ -179,15 +183,16 @@ export class DynamicActionManager { * 2. Optimistically adds it to UI state, and rolls back on failure. * 3. Adds action to `ui_actions` registry. * - * @todo `triggerId` should not be optional. - * * @param action Dynamic action for which to create an event. - * @param triggerId Trigger to which to attach the action. + * @param triggers List of triggers to which action should react. */ - public async createEvent(action: SerializedAction, triggerId = 'VALUE_CLICK_TRIGGER') { + public async createEvent( + action: SerializedAction, + triggers: Array + ) { const event: SerializedEvent = { eventId: uuidv4(), - triggerId, + triggers, action, }; @@ -212,16 +217,16 @@ export class DynamicActionManager { * * @param eventId ID of the event to replace. * @param action New action for which to create the event. - * @param triggerId New trigger with which to associate the event. + * @param triggers List of triggers to which action should react. */ public async updateEvent( eventId: string, action: SerializedAction, - triggerId = 'VALUE_CLICK_TRIGGER' + triggers: Array ) { const event: SerializedEvent = { eventId, - triggerId, + triggers, action, }; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts index 820cc59f77028..b3c5749680319 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts @@ -25,7 +25,7 @@ import { SerializedAction } from './types'; */ export interface SerializedEvent { eventId: string; - triggerId: string; + triggers: string[]; action: SerializedAction; } diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 34b256e4a9efb..d1da0b81a7d0e 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -39,6 +39,8 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { + __attachAction: jest.fn(), + unregisterAction: jest.fn(), addTriggerAction: jest.fn(), attachAction: jest.fn(), clear: jest.fn(), diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index deacf61af41a8..da91e4fd909f6 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -102,7 +102,7 @@ export class UiActionsService { return action; }; - protected readonly unregisterAction = (actionId: string): void => { + public readonly unregisterAction = (actionId: string): void => { if (!this.actions.has(actionId)) { throw new Error(`Action [action.id = ${actionId}] is not registered.`); } @@ -133,7 +133,7 @@ export class UiActionsService { // public readonly removeTriggerAction = - protected readonly __attachAction = ( + public readonly __attachAction = ( triggerId: TriggerId, actionId: string ): void => { diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index 60825f893903e..fcbbf2cd8e425 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -170,7 +170,7 @@ test('Create only mode', async () => { await wait(() => expect(notifications.toasts.addSuccess).toBeCalled()); expect(onClose).toBeCalled(); - expect(await mockDynamicActionManager.count()).toBe(1); + expect(await mockDynamicActionManager.state.get().events.length).toBe(1); }); test.todo("Error when can't fetch drilldown list"); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 137be465fcd87..2a7e2f28cb991 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -18,6 +18,9 @@ import { DynamicActionManager, UiActionsSerializedEvent, UiActionsSerializedAction, + VALUE_CLICK_TRIGGER, + SELECT_RANGE_TRIGGER, + TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; import { useContainerState } from '../../../../../../src/plugins/kibana_utils/common'; import { DrilldownListItem } from '../list_manage_drilldowns'; @@ -66,6 +69,11 @@ export function createFlyoutManageDrilldowns({ return (props: ConnectedFlyoutManageDrilldownsProps) => { const isCreateOnly = props.viewMode === 'create'; + const selectedTriggers: Array = React.useMemo( + () => [VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER], + [] + ); + const factoryContext: DrilldownFactoryContext = React.useMemo( () => ({ place: '', @@ -149,18 +157,24 @@ export function createFlyoutManageDrilldowns({ onBack={isCreateOnly ? undefined : () => setRoute(Routes.Manage)} onSubmit={({ actionConfig, actionFactory, name }) => { if (route === Routes.Create) { - createDrilldown({ - name, - config: actionConfig, - factoryId: actionFactory.id, - }); + createDrilldown( + { + name, + config: actionConfig, + factoryId: actionFactory.id, + }, + selectedTriggers + ); } else { - // edit - editDrilldown(currentEditId!, { - name, - config: actionConfig, - factoryId: actionFactory.id, - }); + editDrilldown( + currentEditId!, + { + name, + config: actionConfig, + factoryId: actionFactory.id, + }, + selectedTriggers + ); } if (isCreateOnly) { @@ -270,9 +284,12 @@ function useDrilldownsStateManager( } } - async function createDrilldown(action: UiActionsSerializedAction, triggerId?: string) { + async function createDrilldown( + action: UiActionsSerializedAction, + selectedTriggers: Array + ) { await run(async () => { - await actionManager.createEvent(action, triggerId); + await actionManager.createEvent(action, selectedTriggers); notifications.toasts.addSuccess({ title: toastDrilldownCreated.title, text: toastDrilldownCreated.text(action.name), @@ -283,10 +300,10 @@ function useDrilldownsStateManager( async function editDrilldown( drilldownId: string, action: UiActionsSerializedAction, - triggerId?: string + selectedTriggers: Array ) { await run(async () => { - await actionManager.updateEvent(drilldownId, action, triggerId); + await actionManager.updateEvent(drilldownId, action, selectedTriggers); notifications.toasts.addSuccess({ title: toastDrilldownEdited.title, text: toastDrilldownEdited.text(action.name), diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index a024c4ad38748..b8deaa8b842bc 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -9,6 +9,7 @@ import { DynamicActionManager, DynamicActionManagerState, UiActionsSerializedAction, + TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common'; @@ -29,11 +30,11 @@ class MockDynamicActionManager implements PublicMethodsOf async createEvent( action: UiActionsSerializedAction, - triggerId: string = 'VALUE_CLICK_TRIGGER' + triggers: Array ) { const event = { action, - triggerId, + triggers, eventId: uuid(), }; const state = this.state.get(); @@ -60,7 +61,7 @@ class MockDynamicActionManager implements PublicMethodsOf async updateEvent( eventId: string, action: UiActionsSerializedAction, - triggerId: string = 'VALUE_CLICK_TRIGGER' + triggers: Array ) { const state = this.state.get(); const events = state.events; @@ -68,7 +69,7 @@ class MockDynamicActionManager implements PublicMethodsOf const event = { eventId, action, - triggerId, + triggers, }; this.state.set({ From 5418305ae45561b707065a83104d02380998b505 Mon Sep 17 00:00:00 2001 From: Andrea Del Rio Date: Tue, 17 Mar 2020 03:19:49 -0700 Subject: [PATCH 43/79] "Create drilldown" flyout - design cleanup (#60309) * create drilldown flyout cleanup * remove border from selectedActionFactoryContainer * adjust callout in DrilldownHello * update form labels * remove unused file * fix type error Co-authored-by: Anton Dosov --- .../storybook_config/webpack.config.js | 3 ++- .../action_wizard/action_wizard.scss | 6 ------ .../action_wizard/action_wizard.tsx | 2 +- .../drilldown_hello_bar.scss | 3 --- .../drilldown_hello_bar.tsx | 13 +++++++++---- .../flyout_drilldown_wizard.story.tsx | 19 +++++++++++++++++++ .../form_drilldown_wizard.tsx | 2 +- .../components/form_drilldown_wizard/i18n.ts | 4 ++-- 8 files changed, 34 insertions(+), 18 deletions(-) delete mode 100644 x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss diff --git a/packages/kbn-storybook/storybook_config/webpack.config.js b/packages/kbn-storybook/storybook_config/webpack.config.js index 1531c1d22b01b..5b6897e58a8e9 100644 --- a/packages/kbn-storybook/storybook_config/webpack.config.js +++ b/packages/kbn-storybook/storybook_config/webpack.config.js @@ -66,7 +66,8 @@ module.exports = async ({ config }) => { config.module.rules.push({ test: /\.tsx$/, // Exclude example files, as we don't display props info for them - exclude: /\.examples.tsx$/, + // TODO: validated_dual_range throws: https://github.com/elastic/kibana/issues/60356 + exclude: /\.examples.tsx$|validated_dual_range.tsx$/, use: [ // Parse TS comments to create Props tables in the UI require.resolve('react-docgen-typescript-loader'), diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss index db09ff4a57ef9..87ec3f8fc7ec1 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.scss @@ -1,9 +1,3 @@ -.auaActionWizard__selectedActionFactoryContainer { - background-color: $euiColorLightestShade; - padding: $euiSize; - border-radius: $euiBorderRadius; -} - .auaActionWizard__actionFactoryItem { .euiKeyPadMenuItem__label { height: #{$euiSizeXL}; diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 466f22d785685..846f6d41eb30d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -134,7 +134,7 @@ const SelectedActionFactory: React.FC = ({
{showDeselect && ( - onDeselect()}> + onDeselect()}> {txtChangeButton} diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss deleted file mode 100644 index e527485765df3..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.scss +++ /dev/null @@ -1,3 +0,0 @@ -.drdHelloBar__content .euiFlexItem { - margin: $euiSizeL; // increase spacing between elements -} diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index 6c975fe324744..fb3a50eb77f2c 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -10,11 +10,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiTextColor, + EuiText, EuiLink, EuiSpacer, EuiButtonEmpty, + EuiIcon, } from '@elastic/eui'; -import './drilldown_hello_bar.scss'; import { txtHideHelpButtonLabel, txtHelpText, txtViewDocsLinkLabel } from './i18n'; export interface DrilldownHelloBarProps { @@ -30,12 +31,16 @@ export const DrilldownHelloBar: React.FC = ({ }) => { return ( + + + - {txtHelpText} + + {txtHelpText} + {docsLink && ( <> @@ -45,7 +50,7 @@ export const DrilldownHelloBar: React.FC = ({ - {txtHideHelpButtonLabel} + {txtHideHelpButtonLabel}
diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx index f332bfc3cecba..152cd393b9d3e 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.story.tsx @@ -48,4 +48,23 @@ storiesOf('components/FlyoutDrilldownWizard', module) /> ); + }) + .add('open in flyout - edit, just 1 action type', () => { + return ( + {}}> + {}} + drilldownActionFactories={[dashboardFactory]} + initialDrilldownWizardConfig={{ + name: 'My fancy drilldown', + actionFactory: urlFactory as any, + actionConfig: { + url: 'https://elastic.co', + openInNewTab: true, + }, + }} + mode={'edit'} + /> + + ); }); diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index 4d9f0f8e13a77..4c9a7a2e514c6 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -54,7 +54,7 @@ export const FormDrilldownWizard: React.FC = ({ const actionWizard = ( 1 ? txtDrilldownAction : undefined} fullWidth={true} className="drdFormDrilldownWizard__formRow" > diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts index ba6975ebeed3c..e9b19ab0afa97 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/i18n.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const txtNameOfDrilldown = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown', { - defaultMessage: 'Name of drilldown:', + defaultMessage: 'Name', } ); @@ -23,6 +23,6 @@ export const txtUntitledDrilldown = i18n.translate( export const txtDrilldownAction = i18n.translate( 'xpack.drilldowns.components.FormCreateDrilldown.drilldownAction', { - defaultMessage: 'Drilldown action:', + defaultMessage: 'Action', } ); From 45f6f4d80a656e8b61e703c604f7329a81a94122 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 17 Mar 2020 13:01:28 +0100 Subject: [PATCH 44/79] basic unit tests for flyout_create_drildown action --- .../flyout_create_drilldown.test.tsx | 147 ++++++++++++++++++ ...{index.tsx => flyout_create_drilldown.tsx} | 0 .../actions/flyout_create_drilldown/index.ts | 11 ++ 3 files changed, 158 insertions(+) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx rename x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/{index.tsx => flyout_create_drilldown.tsx} (100%) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx new file mode 100644 index 0000000000000..190dc705e3811 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + FlyoutCreateDrilldownAction, + OpenFlyoutAddDrilldownParams, +} from './flyout_create_drilldown'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; +import { + Embeddable, + EmbeddableInput, + ViewMode, +} from '../../../../../../../../src/plugins/embeddable/public'; +import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; +import { + TriggerContextMapping, + UiActionsStart, +} from '../../../../../../../../src/plugins/ui_actions/public'; + +const overlays = coreMock.createStart().overlays; +const drilldowns = drilldownsPluginMock.createStartContract(); +const uiActions = uiActionsPluginMock.createStartContract(); + +const actionParams: OpenFlyoutAddDrilldownParams = { + drilldowns: () => Promise.resolve(drilldowns), + overlays: () => Promise.resolve(overlays), +}; + +test('should create', () => { + expect(() => new FlyoutCreateDrilldownAction(actionParams)).not.toThrow(); +}); + +test('title is a string', () => { + expect(typeof new FlyoutCreateDrilldownAction(actionParams).getDisplayName() === 'string').toBe( + true + ); +}); + +test('icon exists', () => { + expect(typeof new FlyoutCreateDrilldownAction(actionParams).getIconType() === 'string').toBe( + true + ); +}); + +class MockEmbeddable extends Embeddable { + public readonly type = 'mock'; + private readonly triggers: Array = []; + constructor( + initialInput: EmbeddableInput, + params: { uiActions?: UiActionsStart; supportedTriggers?: Array } + ) { + super(initialInput, {}, undefined, params); + this.triggers = params.supportedTriggers ?? []; + } + public render(node: HTMLElement) {} + public reload() {} + public supportedTriggers(): Array { + return this.triggers; + } +} + +describe('isCompatible', () => { + const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); + + function checkCompatibility(params: { + isEdit: boolean; + withUiActions: boolean; + isValueClickTriggerSupported: boolean; + }): Promise { + return drilldownAction.isCompatible({ + embeddable: new MockEmbeddable( + { id: '', viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW }, + { + supportedTriggers: (params.isValueClickTriggerSupported + ? ['VALUE_CLICK_TRIGGER'] + : []) as Array, + uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support + } + ), + }); + } + + test("compatible if dynamicUiActions enabled, 'VALUE_CLICK_TRIGGER' is supported, in edit mode", async () => { + expect( + await checkCompatibility({ + withUiActions: true, + isEdit: true, + isValueClickTriggerSupported: true, + }) + ).toBe(true); + }); + + test('not compatible if dynamicUiActions disabled', async () => { + expect( + await checkCompatibility({ + withUiActions: false, + isEdit: true, + isValueClickTriggerSupported: true, + }) + ).toBe(false); + }); + + test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { + expect( + await checkCompatibility({ + withUiActions: true, + isEdit: true, + isValueClickTriggerSupported: false, + }) + ).toBe(false); + }); + + test('not compatible if in view mode', async () => { + expect( + await checkCompatibility({ + withUiActions: true, + isEdit: false, + isValueClickTriggerSupported: true, + }) + ).toBe(false); + }); +}); + +describe('execute', () => { + const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { + await expect( + drilldownAction.execute({ + embeddable: new MockEmbeddable({ id: '' }, {}), + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Can't execute FlyoutCreateDrilldownAction without dynamicActionsManager"` + ); + }); + + test('should open flyout', async () => { + const spy = jest.spyOn(overlays, 'openFlyout'); + await drilldownAction.execute({ + embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + }); + expect(spy).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx similarity index 100% rename from x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts new file mode 100644 index 0000000000000..4d2db209fc961 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + FlyoutCreateDrilldownAction, + OpenFlyoutAddDrilldownParams, + OPEN_FLYOUT_ADD_DRILLDOWN, +} from './flyout_create_drilldown'; From ceb5cfeebaa7033dfcc8b5832bb64ec26f63a4b2 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 17 Mar 2020 13:36:50 +0100 Subject: [PATCH 45/79] Drilldowns finalize (#60371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 align flyout content to left side * fix: 🐛 move context menu item number 1px lower * fix: 🐛 move flyout back nav chevron up * fix: 🐛 fix type check after refactor --- .../embeddable_action_storage.test.ts | 58 +++++++++---------- .../flyout_edit_drilldown/menu_item.tsx | 2 +- .../drilldown_hello_bar.tsx | 4 +- .../components/flyout_frame/flyout_frame.tsx | 14 +++-- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts index eada20721d3e0..83fd3f184e098 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -45,7 +45,7 @@ describe('EmbeddableActionStorage', () => { const storage = new EmbeddableActionStorage(embeddable); const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -63,7 +63,7 @@ describe('EmbeddableActionStorage', () => { const storage = new EmbeddableActionStorage(embeddable); const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -81,17 +81,17 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -115,7 +115,7 @@ describe('EmbeddableActionStorage', () => { const storage = new EmbeddableActionStorage(embeddable); const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -142,14 +142,14 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'bar', } as any, @@ -168,28 +168,28 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'bar', } as any, }; const event22: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'baz', } as any, }; const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'qux', } as any, @@ -219,7 +219,7 @@ describe('EmbeddableActionStorage', () => { const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -237,12 +237,12 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -269,7 +269,7 @@ describe('EmbeddableActionStorage', () => { const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -286,21 +286,21 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'bar', } as any, }; const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: { name: 'qux', } as any, @@ -347,7 +347,7 @@ describe('EmbeddableActionStorage', () => { const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -375,7 +375,7 @@ describe('EmbeddableActionStorage', () => { const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -403,7 +403,7 @@ describe('EmbeddableActionStorage', () => { const event: UiActionsSerializedEvent = { eventId: 'EVENT_ID', - triggerId: 'TRIGGER-ID', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -422,17 +422,17 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID1', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID2', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event3: UiActionsSerializedEvent = { eventId: 'EVENT_ID3', - triggerId: 'TRIGGER-ID3', + triggers: ['TRIGGER-ID'], action: {} as any, }; @@ -476,7 +476,7 @@ describe('EmbeddableActionStorage', () => { await storage.create({ eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID1', + triggers: ['TRIGGER-ID'], action: {} as any, }); @@ -484,7 +484,7 @@ describe('EmbeddableActionStorage', () => { await storage.create({ eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID1', + triggers: ['TRIGGER-ID'], action: {} as any, }); @@ -522,13 +522,13 @@ describe('EmbeddableActionStorage', () => { const event1: UiActionsSerializedEvent = { eventId: 'EVENT_ID1', - triggerId: 'TRIGGER-ID1', + triggers: ['TRIGGER-ID'], action: {} as any, }; const event2: UiActionsSerializedEvent = { eventId: 'EVENT_ID2', - triggerId: 'TRIGGER-ID1', + triggers: ['TRIGGER-ID'], action: {} as any, }; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx index fd46dafd36b73..94f1485a8cbc3 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx @@ -23,7 +23,7 @@ export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) }, [context.embeddable.dynamicActions, isMounted]); const badge = !count ? null : ( - {count} + {count} ); return ( diff --git a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx index fb3a50eb77f2c..8c6739a8ad6c8 100644 --- a/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx +++ b/x-pack/plugins/drilldowns/public/components/drilldown_hello_bar/drilldown_hello_bar.tsx @@ -35,7 +35,9 @@ export const DrilldownHelloBar: React.FC = ({ title={ - +
+ +
diff --git a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx index fc803af16a266..b55cbd88d0dc0 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_frame/flyout_frame.tsx @@ -42,12 +42,14 @@ export const FlyoutFrame: React.FC = ({ {onBack && ( - +
+ +
)} {title && ( From 6d0b8cb44aa5d115fc489ba01a3f7d85b1c6ceb2 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 17 Mar 2020 15:04:00 +0100 Subject: [PATCH 46/79] basic unit tests for drilldown actions --- .../flyout_create_drilldown.test.tsx | 29 +---- .../flyout_edit_drilldown.test.tsx | 102 ++++++++++++++++++ .../flyout_edit_drilldown.tsx | 71 ++++++++++++ .../actions/flyout_edit_drilldown/index.tsx | 71 +----------- .../flyout_edit_drilldown/menu_item.test.tsx | 37 +++++++ .../flyout_edit_drilldown/menu_item.tsx | 32 +++--- .../drilldowns/actions/test_helpers.ts | 28 +++++ 7 files changed, 260 insertions(+), 110 deletions(-) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index 190dc705e3811..31ee9e29938cb 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -10,16 +10,10 @@ import { } from './flyout_create_drilldown'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; -import { - Embeddable, - EmbeddableInput, - ViewMode, -} from '../../../../../../../../src/plugins/embeddable/public'; +import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; -import { - TriggerContextMapping, - UiActionsStart, -} from '../../../../../../../../src/plugins/ui_actions/public'; +import { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; +import { MockEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); @@ -46,23 +40,6 @@ test('icon exists', () => { ); }); -class MockEmbeddable extends Embeddable { - public readonly type = 'mock'; - private readonly triggers: Array = []; - constructor( - initialInput: EmbeddableInput, - params: { uiActions?: UiActionsStart; supportedTriggers?: Array } - ) { - super(initialInput, {}, undefined, params); - this.triggers = params.supportedTriggers ?? []; - } - public render(node: HTMLElement) {} - public reload() {} - public supportedTriggers(): Array { - return this.triggers; - } -} - describe('isCompatible', () => { const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx new file mode 100644 index 0000000000000..a3f11eb976f90 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_edit_drilldown'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; +import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; +import { MockEmbeddable } from '../test_helpers'; + +const overlays = coreMock.createStart().overlays; +const drilldowns = drilldownsPluginMock.createStartContract(); +const uiActions = uiActionsPluginMock.createStartContract(); + +const actionParams: FlyoutEditDrilldownParams = { + drilldowns: () => Promise.resolve(drilldowns), + overlays: () => Promise.resolve(overlays), +}; + +test('should create', () => { + expect(() => new FlyoutEditDrilldownAction(actionParams)).not.toThrow(); +}); + +test('title is a string', () => { + expect(typeof new FlyoutEditDrilldownAction(actionParams).getDisplayName() === 'string').toBe( + true + ); +}); + +test('icon exists', () => { + expect(typeof new FlyoutEditDrilldownAction(actionParams).getIconType() === 'string').toBe(true); +}); + +test('MenuItem exists', () => { + expect(new FlyoutEditDrilldownAction(actionParams).MenuItem).toBeDefined(); +}); + +describe('isCompatible', () => { + const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + + function checkCompatibility(params: { + isEdit: boolean; + withUiActions: boolean; + }): Promise { + return drilldownAction.isCompatible({ + embeddable: new MockEmbeddable( + { + id: '', + viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW, + }, + { + uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support + } + ), + }); + } + + // TODO: need proper DynamicActionsMock and ActionFactory mock + test.todo('compatible if dynamicUiActions enabled, in edit view, and have at least 1 drilldown'); + + test('not compatible if dynamicUiActions disabled', async () => { + expect( + await checkCompatibility({ + withUiActions: false, + isEdit: true, + }) + ).toBe(false); + }); + + test('not compatible if no drilldowns', async () => { + expect( + await checkCompatibility({ + withUiActions: true, + isEdit: true, + }) + ).toBe(false); + }); +}); + +describe('execute', () => { + const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { + await expect( + drilldownAction.execute({ + embeddable: new MockEmbeddable({ id: '' }, {}), + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Can't execute FlyoutEditDrilldownAction without dynamicActionsManager"` + ); + }); + + test('should open flyout', async () => { + const spy = jest.spyOn(overlays, 'openFlyout'); + await drilldownAction.execute({ + embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + }); + expect(spy).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx new file mode 100644 index 0000000000000..91be24ab0087c --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { CoreStart } from 'src/core/public'; +import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; +import { + reactToUiComponent, + toMountPoint, +} from '../../../../../../../../src/plugins/kibana_react/public'; +import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; +import { txtDisplayName } from './i18n'; +import { MenuItem } from './menu_item'; + +export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; + +export interface FlyoutEditDrilldownParams { + overlays: () => Promise; + drilldowns: () => Promise; +} + +export class FlyoutEditDrilldownAction implements ActionByType { + public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; + public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; + public order = 10; + + constructor(protected readonly params: FlyoutEditDrilldownParams) {} + + public getDisplayName() { + return txtDisplayName; + } + + public getIconType() { + return 'list'; + } + + MenuItem = reactToUiComponent(MenuItem); + + public async isCompatible({ embeddable }: EmbeddableContext) { + if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; + if (!embeddable.dynamicActions) return false; + return embeddable.dynamicActions.state.get().events.length > 0; + } + + public async execute(context: EmbeddableContext) { + const overlays = await this.params.overlays(); + const drilldowns = await this.params.drilldowns(); + const dynamicActionManager = context.embeddable.dynamicActions; + if (!dynamicActionManager) { + throw new Error(`Can't execute FlyoutEditDrilldownAction without dynamicActionsManager`); + } + + const handle = overlays.openFlyout( + toMountPoint( + handle.close()} + context={context} + viewMode={'manage'} + dynamicActionManager={dynamicActionManager} + /> + ), + { + ownFocus: true, + } + ); + } +} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx index b8d20731eecd3..3e1b37f270708 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/index.tsx @@ -4,69 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { CoreStart } from 'src/core/public'; -import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; -import { - reactToUiComponent, - toMountPoint, -} from '../../../../../../../../src/plugins/kibana_react/public'; -import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; -import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; -import { txtDisplayName } from './i18n'; -import { MenuItem } from './menu_item'; - -export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; - -export interface FlyoutEditDrilldownParams { - overlays: () => Promise; - drilldowns: () => Promise; -} - -export class FlyoutEditDrilldownAction implements ActionByType { - public readonly type = OPEN_FLYOUT_EDIT_DRILLDOWN; - public readonly id = OPEN_FLYOUT_EDIT_DRILLDOWN; - public order = 10; - - constructor(protected readonly params: FlyoutEditDrilldownParams) {} - - public getDisplayName() { - return txtDisplayName; - } - - public getIconType() { - return 'list'; - } - - MenuItem = reactToUiComponent(MenuItem); - - public async isCompatible({ embeddable }: EmbeddableContext) { - if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; - if (!embeddable.dynamicActions) return false; - - return (await embeddable.dynamicActions.count()) > 0; - } - - public async execute(context: EmbeddableContext) { - const overlays = await this.params.overlays(); - const drilldowns = await this.params.drilldowns(); - const dynamicActionManager = context.embeddable.dynamicActions; - if (!dynamicActionManager) { - throw new Error(`Can't execute FlyoutEditDrilldownAction without dynamicActionsManager`); - } - - const handle = overlays.openFlyout( - toMountPoint( - handle.close()} - context={context} - viewMode={'manage'} - dynamicActionManager={dynamicActionManager} - /> - ), - { - ownFocus: true, - } - ); - } -} +export { + FlyoutEditDrilldownAction, + FlyoutEditDrilldownParams, + OPEN_FLYOUT_EDIT_DRILLDOWN, +} from './flyout_edit_drilldown'; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx new file mode 100644 index 0000000000000..be693fadf9282 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, cleanup, act } from '@testing-library/react/pure'; +import { MenuItem } from './menu_item'; +import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/common'; +import { DynamicActionManager } from '../../../../../../../../src/plugins/ui_actions/public'; +import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public/lib/embeddables'; +import '@testing-library/jest-dom'; + +afterEach(cleanup); + +test('', () => { + const state = createStateContainer<{ events: object[] }>({ events: [] }); + const { getByText, queryByText } = render( + + ); + + expect(getByText(/manage drilldowns/i)).toBeInTheDocument(); + expect(queryByText('0')).not.toBeInTheDocument(); + + act(() => { + state.set({ events: [{}] }); + }); + + expect(queryByText('1')).toBeInTheDocument(); +}); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx index fd46dafd36b73..4f99fca511b07 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx @@ -5,30 +5,26 @@ */ import React from 'react'; -import { EuiNotificationBadge } from '@elastic/eui'; -import useMountedState from 'react-use/lib/useMountedState'; +import { EuiNotificationBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; import { txtDisplayName } from './i18n'; +import { useContainerState } from '../../../../../../../../src/plugins/kibana_utils/common'; export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) => { - const isMounted = useMountedState(); - const [count, setCount] = React.useState(0); + if (!context.embeddable.dynamicActions) + throw new Error('Flyout edit drillldown context menu item requires `dynamicActions`'); - React.useEffect(() => { - if (!context.embeddable.dynamicActions) return; - context.embeddable.dynamicActions.count().then(result => { - if (!isMounted()) return; - setCount(result); - }); - }, [context.embeddable.dynamicActions, isMounted]); - - const badge = !count ? null : ( - {count} - ); + const { events } = useContainerState(context.embeddable.dynamicActions.state); + const count = events.length; return ( - <> - {txtDisplayName} {badge} - + + {txtDisplayName} + {count > 0 && ( + + {count} + + )} + ); }; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts new file mode 100644 index 0000000000000..9b156b0ba85b4 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public/'; +import { + TriggerContextMapping, + UiActionsStart, +} from '../../../../../../../src/plugins/ui_actions/public'; + +export class MockEmbeddable extends Embeddable { + public readonly type = 'mock'; + private readonly triggers: Array = []; + constructor( + initialInput: EmbeddableInput, + params: { uiActions?: UiActionsStart; supportedTriggers?: Array } + ) { + super(initialInput, {}, undefined, params); + this.triggers = params.supportedTriggers ?? []; + } + public render(node: HTMLElement) {} + public reload() {} + public supportedTriggers(): Array { + return this.triggers; + } +} From 1252f37eb2df6efc6bc5564f64a3d6e0eacd7fde Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 18 Mar 2020 14:41:51 +0100 Subject: [PATCH 47/79] Drilldowns finalize 2 (#60510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 fix test mock * chore: 🤖 remove unused UiActionsService methods * refactor: 💡 cleanup UiActionsService action registration * fix: 🐛 add missing functionality after refactor * test: 💍 add action factory tests * test: 💍 add DynamicActionManager tests * feat: 🎸 capture error if it happens during initial load * fix: 🐛 register correctly CSV action --- examples/ui_action_examples/public/plugin.ts | 2 +- examples/ui_actions_explorer/public/app.tsx | 3 +- .../ui_actions_explorer/public/plugin.tsx | 16 +- src/plugins/dashboard/public/plugin.tsx | 4 +- .../public/tests/dashboard_container.test.tsx | 2 +- src/plugins/data/public/plugin.ts | 9 +- src/plugins/kibana_utils/index.ts | 2 +- .../actions/dynamic_action_manager.test.ts | 623 +++++++++++++++++- .../public/actions/dynamic_action_manager.ts | 25 +- .../actions/dynamic_action_manager_state.ts | 16 + .../public/actions/dynamic_action_storage.ts | 40 +- src/plugins/ui_actions/public/mocks.ts | 8 +- src/plugins/ui_actions/public/plugin.ts | 8 +- .../public/service/ui_actions_service.test.ts | 111 +++- .../public/service/ui_actions_service.ts | 80 +-- .../tests/execute_trigger_actions.test.ts | 10 +- .../public/tests/get_trigger_actions.test.ts | 4 +- .../get_trigger_compatible_actions.test.ts | 6 +- .../public/np_ready/public/plugin.tsx | 3 +- .../public/sample_panel_action.tsx | 3 +- .../public/sample_panel_link.ts | 3 +- .../advanced_ui_actions/public/plugin.ts | 6 +- .../dashboard_drilldowns_services.ts | 6 +- x-pack/plugins/reporting/public/plugin.tsx | 3 +- 24 files changed, 820 insertions(+), 173 deletions(-) diff --git a/examples/ui_action_examples/public/plugin.ts b/examples/ui_action_examples/public/plugin.ts index c47746d4b3fd6..d053f7e82862c 100644 --- a/examples/ui_action_examples/public/plugin.ts +++ b/examples/ui_action_examples/public/plugin.ts @@ -46,7 +46,7 @@ export class UiActionExamplesPlugin })); uiActions.registerAction(helloWorldAction); - uiActions.attachAction(helloWorldTrigger.id, helloWorldAction); + uiActions.addTriggerAction(helloWorldTrigger.id, helloWorldAction); } public start() {} diff --git a/examples/ui_actions_explorer/public/app.tsx b/examples/ui_actions_explorer/public/app.tsx index 462f5c3bf88ba..f08b8bb29bdd3 100644 --- a/examples/ui_actions_explorer/public/app.tsx +++ b/examples/ui_actions_explorer/public/app.tsx @@ -95,8 +95,7 @@ const ActionsExplorer = ({ uiActionsApi, openModal }: Props) => { ); }, }); - uiActionsApi.registerAction(dynamicAction); - uiActionsApi.attachAction(HELLO_WORLD_TRIGGER_ID, dynamicAction); + uiActionsApi.addTriggerAction(HELLO_WORLD_TRIGGER_ID, dynamicAction); setConfirmationText( `You've successfully added a new action: ${dynamicAction.getDisplayName( {} diff --git a/examples/ui_actions_explorer/public/plugin.tsx b/examples/ui_actions_explorer/public/plugin.tsx index f1895905a45e1..de86b51aee3a8 100644 --- a/examples/ui_actions_explorer/public/plugin.tsx +++ b/examples/ui_actions_explorer/public/plugin.tsx @@ -79,21 +79,21 @@ export class UiActionsExplorerPlugin implements Plugin (await startServices)[1].uiActions) ); - deps.uiActions.attachAction( + deps.uiActions.addTriggerAction( USER_TRIGGER, createEditUserAction(async () => (await startServices)[0].overlays.openModal) ); - deps.uiActions.attachAction(COUNTRY_TRIGGER, viewInMapsAction); - deps.uiActions.attachAction(COUNTRY_TRIGGER, lookUpWeatherAction); - deps.uiActions.attachAction(COUNTRY_TRIGGER, showcasePluggability); - deps.uiActions.attachAction(PHONE_TRIGGER, makePhoneCallAction); - deps.uiActions.attachAction(PHONE_TRIGGER, showcasePluggability); - deps.uiActions.attachAction(USER_TRIGGER, showcasePluggability); + deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, viewInMapsAction); + deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, lookUpWeatherAction); + deps.uiActions.addTriggerAction(COUNTRY_TRIGGER, showcasePluggability); + deps.uiActions.addTriggerAction(PHONE_TRIGGER, makePhoneCallAction); + deps.uiActions.addTriggerAction(PHONE_TRIGGER, showcasePluggability); + deps.uiActions.addTriggerAction(USER_TRIGGER, showcasePluggability); core.application.register({ id: 'uiActionsExplorer', diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 8a6e747aac170..d663c736e5aed 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -78,7 +78,7 @@ export class DashboardEmbeddableContainerPublicPlugin ): Setup { const expandPanelAction = new ExpandPanelAction(); uiActions.registerAction(expandPanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); const startServices = core.getStartServices(); if (share) { @@ -134,7 +134,7 @@ export class DashboardEmbeddableContainerPublicPlugin plugins.embeddable.getEmbeddableFactories ); uiActions.registerAction(changeViewAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, changeViewAction); } public stop() {} diff --git a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx index a81d80b440e04..4aede3f3442fb 100644 --- a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/tests/dashboard_container.test.tsx @@ -49,7 +49,7 @@ test('DashboardContainer in edit mode shows edit mode actions', async () => { const editModeAction = createEditModeAction(); uiActionsSetup.registerAction(editModeAction); - uiActionsSetup.attachAction(CONTEXT_MENU_TRIGGER, editModeAction); + uiActionsSetup.addTriggerAction(CONTEXT_MENU_TRIGGER, editModeAction); setup.registerEmbeddableFactory( CONTACT_CARD_EMBEDDABLE, new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index a01c133712206..1dbaed6aac6bd 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -108,12 +108,12 @@ export class DataPublicPlugin implements Plugin { +const actionFactoryDefinition1: ActionFactoryDefinition = { + id: 'ACTION_FACTORY_1', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: (() => true) as any, + create: () => ({ + id: '', + execute: async () => {}, + }), +}; + +const actionFactoryDefinition2: ActionFactoryDefinition = { + id: 'ACTION_FACTORY_2', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: (() => true) as any, + create: () => ({ + id: '', + execute: async () => {}, + }), +}; + +const event1: SerializedEvent = { + eventId: 'EVENT_ID_1', + triggers: ['VALUE_CLICK_TRIGGER'], + action: { + factoryId: actionFactoryDefinition1.id, + name: 'Action 1', + config: {}, + }, +}; + +const event2: SerializedEvent = { + eventId: 'EVENT_ID_2', + triggers: ['VALUE_CLICK_TRIGGER'], + action: { + factoryId: actionFactoryDefinition1.id, + name: 'Action 2', + config: {}, + }, +}; + +const event3: SerializedEvent = { + eventId: 'EVENT_ID_3', + triggers: ['VALUE_CLICK_TRIGGER'], + action: { + factoryId: actionFactoryDefinition2.id, + name: 'Action 3', + config: {}, + }, +}; + +const setup = (events: readonly SerializedEvent[] = []) => { const isCompatible = async () => true; - const storage: DynamicActionManagerParams['storage'] = { - count: jest.fn(), - create: jest.fn(), - list: jest.fn(), - read: jest.fn(), - remove: jest.fn(), - update: jest.fn(), - }; - const uiActions: DynamicActionManagerParams['uiActions'] = { - addTriggerAction: jest.fn(), - getActionFactory: jest.fn(), - removeTriggerAction: jest.fn(), - }; + const storage: ActionStorage = new MemoryActionStorage(events); + const actions: ActionRegistry = new Map(); + const uiActions = new UiActionsService({ + actions, + }); const manager = new DynamicActionManager({ isCompatible, storage, uiActions, }); + uiActions.registerTrigger({ + id: 'VALUE_CLICK_TRIGGER', + }); + return { isCompatible, + actions, storage, uiActions, manager, @@ -50,44 +105,540 @@ const setup = () => { describe('DynamicActionManager', () => { test('can instantiate', () => { - const { manager } = setup(); + const { manager } = setup([event1]); expect(manager).toBeInstanceOf(DynamicActionManager); }); describe('.start()', () => { - test.todo('instantiates stored events'); - test.todo('does nothing when no events stored'); + test('instantiates stored events', async () => { + const { manager, actions, uiActions } = setup([event1]); + const create1 = jest.fn(); + const create2 = jest.fn(); + + uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); + uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); + + expect(create1).toHaveBeenCalledTimes(0); + expect(create2).toHaveBeenCalledTimes(0); + expect(actions.size).toBe(0); + + await manager.start(); + + expect(create1).toHaveBeenCalledTimes(1); + expect(create2).toHaveBeenCalledTimes(0); + expect(actions.size).toBe(1); + }); + + test('does nothing when no events stored', async () => { + const { manager, actions, uiActions } = setup(); + const create1 = jest.fn(); + const create2 = jest.fn(); + + uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); + uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); + + expect(create1).toHaveBeenCalledTimes(0); + expect(create2).toHaveBeenCalledTimes(0); + expect(actions.size).toBe(0); + + await manager.start(); + + expect(create1).toHaveBeenCalledTimes(0); + expect(create2).toHaveBeenCalledTimes(0); + expect(actions.size).toBe(0); + }); + + test('UI state is empty before manager starts', async () => { + const { manager } = setup([event1]); + + expect(manager.state.get()).toMatchObject({ + events: [], + isFetchingEvents: false, + fetchCount: 0, + }); + }); + + test('loads events into UI state', async () => { + const { manager, uiActions } = setup([event1, event2, event3]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + uiActions.registerActionFactory(actionFactoryDefinition2); + + await manager.start(); + + expect(manager.state.get()).toMatchObject({ + events: [event1, event2, event3], + isFetchingEvents: false, + fetchCount: 1, + }); + }); + + test('sets isFetchingEvents to true while fetching events', async () => { + const { manager, uiActions } = setup([event1, event2, event3]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + uiActions.registerActionFactory(actionFactoryDefinition2); + + const promise = manager.start().catch(() => {}); + + expect(manager.state.get().isFetchingEvents).toBe(true); + + await promise; + + expect(manager.state.get().isFetchingEvents).toBe(false); + }); + + test('throws if storage threw', async () => { + const { manager, storage } = setup([event1]); + + storage.list = async () => { + throw new Error('baz'); + }; + + const [, error] = await of(manager.start()); + + expect(error).toEqual(new Error('baz')); + }); + + test('sets UI state error if error happened during initial fetch', async () => { + const { manager, storage } = setup([event1]); + + storage.list = async () => { + throw new Error('baz'); + }; + + await of(manager.start()); + + expect(manager.state.get().fetchError!.message).toBe('baz'); + }); }); describe('.stop()', () => { - test.todo('removes events from UI actions registry'); - test.todo('does nothing when no events stored'); + test('removes events from UI actions registry', async () => { + const { manager, actions, uiActions } = setup([event1, event2]); + const create1 = jest.fn(); + const create2 = jest.fn(); + + uiActions.registerActionFactory({ ...actionFactoryDefinition1, create: create1 }); + uiActions.registerActionFactory({ ...actionFactoryDefinition2, create: create2 }); + + expect(actions.size).toBe(0); + + await manager.start(); + + expect(actions.size).toBe(2); + + await manager.stop(); + + expect(actions.size).toBe(0); + }); }); describe('.createEvent()', () => { - test.todo('stores new event in storage'); - test.todo('instantiates event in actions service'); + describe('when storage succeeds', () => { + test('stores new event in storage', async () => { + const { manager, storage, uiActions } = setup([]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + expect(await storage.count()).toBe(0); + + await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); + + expect(await storage.count()).toBe(1); + + const [event] = await storage.list(); + + expect(event).toMatchObject({ + eventId: expect.any(String), + triggers: ['VALUE_CLICK_TRIGGER'], + action: { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }, + }); + }); + + test('adds event to UI state', async () => { + const { manager, uiActions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(manager.state.get().events.length).toBe(0); + + await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); + + expect(manager.state.get().events.length).toBe(1); + }); + + test('optimistically adds event to UI state', async () => { + const { manager, uiActions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(manager.state.get().events.length).toBe(0); + + const promise = manager.createEvent(action, ['VALUE_CLICK_TRIGGER']).catch(e => e); + + expect(manager.state.get().events.length).toBe(1); + + await promise; + + expect(manager.state.get().events.length).toBe(1); + }); + + test('instantiates event in actions service', async () => { + const { manager, uiActions, actions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(actions.size).toBe(0); + + await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); + + expect(actions.size).toBe(1); + }); + }); + + describe('when storage fails', () => { + test('throws an error', async () => { + const { manager, storage, uiActions } = setup([]); + + storage.create = async () => { + throw new Error('foo'); + }; + + uiActions.registerActionFactory(actionFactoryDefinition1); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + const [, error] = await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); + + expect(error).toEqual(new Error('foo')); + }); + + test('does not add even to UI state', async () => { + const { manager, storage, uiActions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + storage.create = async () => { + throw new Error('foo'); + }; + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); + + expect(manager.state.get().events.length).toBe(0); + }); + + test('optimistically adds event to UI state and then removes it', async () => { + const { manager, storage, uiActions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + storage.create = async () => { + throw new Error('foo'); + }; + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(manager.state.get().events.length).toBe(0); + + const promise = manager.createEvent(action, ['VALUE_CLICK_TRIGGER']).catch(e => e); + + expect(manager.state.get().events.length).toBe(1); + + await promise; + + expect(manager.state.get().events.length).toBe(0); + }); + + test('does not instantiate event in actions service', async () => { + const { manager, storage, uiActions, actions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + storage.create = async () => { + throw new Error('foo'); + }; + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(actions.size).toBe(0); + + await of(manager.createEvent(action, ['VALUE_CLICK_TRIGGER'])); + + expect(actions.size).toBe(0); + }); + }); }); describe('.updateEvent()', () => { - test.todo('removes old event from ui actions service'); - test.todo('updates event in storage'); - test.todo('adds new event to ui actions service'); - }); + describe('when storage succeeds', () => { + test('un-registers old event from ui actions service and registers the new one', async () => { + const { manager, actions, uiActions } = setup([event3]); - describe('.deleteEvents()', () => { - test.todo('removes all actions from ui actions service'); - test.todo('removes all events from storage'); - describe('when event is removed from storage its action is also killed', () => { - test.todo('when subsequent event fails to be removed from storage'); + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + expect(actions.size).toBe(1); + + const registeredAction1 = actions.values().next().value; + + expect(registeredAction1.getDisplayName()).toBe('Action 3'); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); + + expect(actions.size).toBe(1); + + const registeredAction2 = actions.values().next().value; + + expect(registeredAction2.getDisplayName()).toBe('foo'); + }); + + test('updates event in storage', async () => { + const { manager, storage, uiActions } = setup([event3]); + const storageUpdateSpy = jest.spyOn(storage, 'update'); + + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + expect(storageUpdateSpy).toHaveBeenCalledTimes(0); + + await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); + + expect(storageUpdateSpy).toHaveBeenCalledTimes(1); + expect(storageUpdateSpy.mock.calls[0][0]).toMatchObject({ + eventId: expect.any(String), + triggers: ['VALUE_CLICK_TRIGGER'], + action: { + factoryId: actionFactoryDefinition2.id, + }, + }); + }); + + test('updates event in UI state', async () => { + const { manager, uiActions } = setup([event3]); + + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + expect(manager.state.get().events[0].action.name).toBe('Action 3'); + + await manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']); + + expect(manager.state.get().events[0].action.name).toBe('foo'); + }); + + test('optimistically updates event in UI state', async () => { + const { manager, uiActions } = setup([event3]); + + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + expect(manager.state.get().events[0].action.name).toBe('Action 3'); + + const promise = manager + .updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']) + .catch(e => e); + + expect(manager.state.get().events[0].action.name).toBe('foo'); + + await promise; + }); }); - }); - describe('.list()', () => { - test.todo('returns stored events'); + describe('when storage fails', () => { + test('throws error', async () => { + const { manager, storage, uiActions } = setup([event3]); + + storage.update = () => { + throw new Error('bar'); + }; + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + const [, error] = await of( + manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER']) + ); + + expect(error).toEqual(new Error('bar')); + }); + + test('keeps the old action in actions registry', async () => { + const { manager, storage, actions, uiActions } = setup([event3]); + + storage.update = () => { + throw new Error('bar'); + }; + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + expect(actions.size).toBe(1); + + const registeredAction1 = actions.values().next().value; + + expect(registeredAction1.getDisplayName()).toBe('Action 3'); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + await of(manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER'])); + + expect(actions.size).toBe(1); + + const registeredAction2 = actions.values().next().value; + + expect(registeredAction2.getDisplayName()).toBe('Action 3'); + }); + + test('keeps old event in UI state', async () => { + const { manager, storage, uiActions } = setup([event3]); + + storage.update = () => { + throw new Error('bar'); + }; + uiActions.registerActionFactory(actionFactoryDefinition2); + await manager.start(); + + const action: SerializedAction = { + factoryId: actionFactoryDefinition2.id, + name: 'foo', + config: {}, + }; + + expect(manager.state.get().events[0].action.name).toBe('Action 3'); + + await of(manager.updateEvent(event3.eventId, action, ['VALUE_CLICK_TRIGGER'])); + + expect(manager.state.get().events[0].action.name).toBe('Action 3'); + }); + }); }); - describe('.count()', () => { - test.todo('returns number of stored events'); + describe('.deleteEvents()', () => { + describe('when storage succeeds', () => { + test('removes all actions from uiActions service', async () => { + const { manager, actions, uiActions } = setup([event2, event1]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(actions.size).toBe(2); + + await manager.deleteEvents([event1.eventId, event2.eventId]); + + expect(actions.size).toBe(0); + }); + + test('removes all events from storage', async () => { + const { manager, uiActions, storage } = setup([event2, event1]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(await storage.list()).toEqual([event2, event1]); + + await manager.deleteEvents([event1.eventId, event2.eventId]); + + expect(await storage.list()).toEqual([]); + }); + + test('removes all events from UI state', async () => { + const { manager, uiActions } = setup([event2, event1]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(manager.state.get().events).toEqual([event2, event1]); + + await manager.deleteEvents([event1.eventId, event2.eventId]); + + expect(manager.state.get().events).toEqual([]); + }); + }); }); }); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 9655922ef07b8..6e2d0ebe3aae9 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -42,7 +42,7 @@ export interface DynamicActionManagerParams { storage: ActionStorage; uiActions: Pick< UiActionsService, - 'registerAction' | '__attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' + 'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' >; isCompatible: (context: C) => Promise; } @@ -90,7 +90,7 @@ export class DynamicActionManager { }; uiActions.registerAction(actionDefinition); - for (const trigger of triggers) uiActions.__attachAction(trigger as any, actionId); + for (const trigger of triggers) uiActions.attachAction(trigger as any, actionId); } protected killAction({ eventId, triggers }: SerializedEvent) { @@ -150,9 +150,14 @@ export class DynamicActionManager { if (this.ui.get().isFetchingEvents) return; this.ui.transitions.startFetching(); - const events = await this.params.storage.list(); - for (const event of events) this.reviveAction(event); - this.ui.transitions.finishFetching(events); + try { + const events = await this.params.storage.list(); + for (const event of events) this.reviveAction(event); + this.ui.transitions.finishFetching(events); + } catch (error) { + this.ui.transitions.failFetching(error instanceof Error ? error : { message: String(error) }); + throw error; + } if (this.params.storage.reload$) { this.reloadSubscription = this.params.storage.reload$.subscribe(this.onSync); @@ -200,8 +205,9 @@ export class DynamicActionManager { try { await this.params.storage.create(event); this.reviveAction(event); - } catch { + } catch (error) { this.ui.transitions.removeEvent(event.eventId); + throw error; } } @@ -229,7 +235,6 @@ export class DynamicActionManager { triggers, action, }; - const oldEvent = this.getEvent(eventId); this.killAction(oldEvent); @@ -238,10 +243,11 @@ export class DynamicActionManager { try { await this.params.storage.update(event); - } catch { + } catch (error) { this.killAction(event); this.reviveAction(oldEvent); this.ui.transitions.replaceEvent(oldEvent); + throw error; } } @@ -262,9 +268,10 @@ export class DynamicActionManager { try { await this.params.storage.remove(eventId); - } catch { + } catch (error) { this.reviveAction(event); this.ui.transitions.addEvent(event); + throw error; } } diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts index ba42fc4d15ce7..636af076ea39f 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts @@ -35,6 +35,13 @@ export interface State { */ readonly fetchCount: number; + /** + * Error received last time when fetching events. + */ + readonly fetchError?: { + message: string; + }; + /** * List of all fetched events. */ @@ -44,6 +51,7 @@ export interface State { export interface Transitions { startFetching: (state: State) => () => State; finishFetching: (state: State) => (events: SerializedEvent[]) => State; + failFetching: (state: State) => (error: { message: string }) => State; addEvent: (state: State) => (event: SerializedEvent) => State; removeEvent: (state: State) => (eventId: string) => State; replaceEvent: (state: State) => (event: SerializedEvent) => State; @@ -66,9 +74,17 @@ export const transitions: Transitions = { ...state, isFetchingEvents: false, fetchCount: state.fetchCount + 1, + fetchError: undefined, events, }), + failFetching: state => ({ message }) => ({ + ...state, + isFetchingEvents: false, + fetchCount: state.fetchCount + 1, + fetchError: { message }, + }), + addEvent: state => (event: SerializedEvent) => ({ ...state, events: [...state.events, event], diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts index b3c5749680319..28550a671782e 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts @@ -17,6 +17,8 @@ * under the License. */ +/* eslint-disable max-classes-per-file */ + import { Observable, Subject } from 'rxjs'; import { SerializedAction } from './types'; @@ -56,9 +58,45 @@ export abstract class AbstractActionStorage implements ActionStorage { return (await this.list()).length; } + public async read(eventId: string): Promise { + const events = await this.list(); + const event = events.find(ev => ev.eventId === eventId); + if (!event) throw new Error(`Event [eventId = ${eventId}] not found.`); + return event; + } + abstract create(event: SerializedEvent): Promise; abstract update(event: SerializedEvent): Promise; abstract remove(eventId: string): Promise; - abstract read(eventId: string): Promise; abstract list(): Promise; } + +/** + * This is an in-memory implementation of ActionStorage. It is used in testing, + * but can also be used production code to store events in memory. + */ +export class MemoryActionStorage extends AbstractActionStorage { + constructor(public events: readonly SerializedEvent[] = []) { + super(); + } + + public async list() { + return this.events.map(event => ({ ...event })); + } + + public async create(event: SerializedEvent) { + this.events = [...this.events, { ...event }]; + } + + public async update(event: SerializedEvent) { + const index = this.events.findIndex(({ eventId }) => eventId === event.eventId); + if (index < 0) throw new Error(`Event [eventId = ${event.eventId}] not found`); + this.events = [...this.events.slice(0, index), { ...event }, ...this.events.slice(index + 1)]; + } + + public async remove(eventId: string) { + const index = this.events.findIndex(ev => eventId === ev.eventId); + if (index < 0) throw new Error(`Event [eventId = ${eventId}] not found`); + this.events = [...this.events.slice(0, index), ...this.events.slice(index + 1)]; + } +} diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index d1da0b81a7d0e..4de38eb5421e9 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -28,21 +28,22 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { + addTriggerAction: jest.fn(), attachAction: jest.fn(), detachAction: jest.fn(), registerAction: jest.fn(), - registerTrigger: jest.fn(), registerActionFactory: jest.fn(), + registerTrigger: jest.fn(), + unregisterAction: jest.fn(), }; return setupContract; }; const createStartContract = (): Start => { const startContract: Start = { - __attachAction: jest.fn(), + attachAction: jest.fn(), unregisterAction: jest.fn(), addTriggerAction: jest.fn(), - attachAction: jest.fn(), clear: jest.fn(), detachAction: jest.fn(), executeTriggerActions: jest.fn(), @@ -56,7 +57,6 @@ const createStartContract = (): Start => { registerAction: jest.fn(), registerActionFactory: jest.fn(), registerTrigger: jest.fn(), - removeTriggerAction: jest.fn(), }; return startContract; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index f886781c2ee30..88a5cb04eac6f 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -23,7 +23,13 @@ import { selectRangeTrigger, valueClickTrigger, applyFilterTrigger } from './tri export type UiActionsSetup = Pick< UiActionsService, - 'attachAction' | 'detachAction' | 'registerAction' | 'registerTrigger' | 'registerActionFactory' + | 'addTriggerAction' + | 'attachAction' + | 'detachAction' + | 'registerAction' + | 'registerActionFactory' + | 'registerTrigger' + | 'unregisterAction' >; export type UiActionsStart = PublicMethodsOf; diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index 2d946a8d706ab..18262f0945cf8 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -18,7 +18,13 @@ */ import { UiActionsService } from './ui_actions_service'; -import { Action, ActionInternal, createAction } from '../actions'; +import { + Action, + ActionInternal, + createAction, + ActionFactoryDefinition, + ActionFactory, +} from '../actions'; import { createHelloWorldAction } from '../tests/test_samples'; import { ActionRegistry, TriggerRegistry, TriggerId, ActionType } from '../types'; import { Trigger } from '../triggers'; @@ -103,7 +109,20 @@ describe('UiActionsService', () => { }); }); - test.todo('return action instance'); + test('return action instance', () => { + const service = new UiActionsService(); + const action = service.registerAction({ + id: 'test', + execute: async () => {}, + getDisplayName: () => 'test', + getIconType: () => '', + isCompatible: async () => true, + type: 'test' as ActionType, + }); + + expect(action).toBeInstanceOf(ActionInternal); + expect(action.id).toBe('test'); + }); }); describe('.getTriggerActions()', () => { @@ -141,14 +160,14 @@ describe('UiActionsService', () => { expect(list0).toHaveLength(0); - service.attachAction(FOO_TRIGGER, action1); + service.addTriggerAction(FOO_TRIGGER, action1); const list1 = service.getTriggerActions(FOO_TRIGGER); expect(list1).toHaveLength(1); expect(list1[0]).toBeInstanceOf(ActionInternal); expect(list1[0].id).toBe(action1.id); - service.attachAction(FOO_TRIGGER, action2); + service.addTriggerAction(FOO_TRIGGER, action2); const list2 = service.getTriggerActions(FOO_TRIGGER); expect(list2).toHaveLength(2); @@ -181,7 +200,7 @@ describe('UiActionsService', () => { title: 'My trigger', }; service.registerTrigger(testTrigger); - service.attachAction(MY_TRIGGER, helloWorldAction); + service.addTriggerAction(MY_TRIGGER, helloWorldAction); const compatibleActions = await service.getTriggerCompatibleActions(MY_TRIGGER, { hi: 'there', @@ -207,7 +226,7 @@ describe('UiActionsService', () => { }; service.registerTrigger(testTrigger); - service.attachAction(testTrigger.id, action); + service.addTriggerAction(testTrigger.id, action); const compatibleActions1 = await service.getTriggerCompatibleActions(testTrigger.id, { accept: true, @@ -291,7 +310,7 @@ describe('UiActionsService', () => { id: FOO_TRIGGER, }); service1.registerAction(testAction1); - service1.attachAction(FOO_TRIGGER, testAction1); + service1.addTriggerAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); @@ -312,14 +331,14 @@ describe('UiActionsService', () => { }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.attachAction(FOO_TRIGGER, testAction1); + service1.addTriggerAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service2.attachAction(FOO_TRIGGER, testAction2); + service2.addTriggerAction(FOO_TRIGGER, testAction2); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); @@ -333,14 +352,14 @@ describe('UiActionsService', () => { }); service1.registerAction(testAction1); service1.registerAction(testAction2); - service1.attachAction(FOO_TRIGGER, testAction1); + service1.addTriggerAction(FOO_TRIGGER, testAction1); const service2 = service1.fork(); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); - service1.attachAction(FOO_TRIGGER, testAction2); + service1.addTriggerAction(FOO_TRIGGER, testAction2); expect(service1.getTriggerActions(FOO_TRIGGER)).toHaveLength(2); expect(service2.getTriggerActions(FOO_TRIGGER)).toHaveLength(1); @@ -395,7 +414,7 @@ describe('UiActionsService', () => { } as any; service.registerTrigger(trigger); - service.attachAction(MY_TRIGGER, action); + service.addTriggerAction(MY_TRIGGER, action); const actions = service.getTriggerActions(trigger.id); @@ -403,7 +422,7 @@ describe('UiActionsService', () => { expect(actions[0].id).toBe(ACTION_HELLO_WORLD); }); - test('can detach an action to a trigger', () => { + test('can detach an action from a trigger', () => { const service = new UiActionsService(); const trigger: Trigger = { @@ -416,7 +435,7 @@ describe('UiActionsService', () => { service.registerTrigger(trigger); service.registerAction(action); - service.attachAction(trigger.id, action); + service.addTriggerAction(trigger.id, action); service.detachAction(trigger.id, action.id); const actions2 = service.getTriggerActions(trigger.id); @@ -448,7 +467,7 @@ describe('UiActionsService', () => { } as any; service.registerAction(action); - expect(() => service.attachAction('i do not exist' as TriggerId, action)).toThrowError( + expect(() => service.addTriggerAction('i do not exist' as TriggerId, action)).toThrowError( 'No trigger [triggerId = i do not exist] exists, for attaching action [actionId = ACTION_HELLO_WORLD].' ); }); @@ -480,10 +499,62 @@ describe('UiActionsService', () => { }); describe('action factories', () => { - test.todo('.getActionFactories() returns empty array if no action factories registered'); - test.todo('can register an action factory'); - test.todo('can retrieve all action factories'); - test.todo('can retrieve action factory by ID'); - test.todo('throws when retrieving action factory that does not exist'); + const factoryDefinition1: ActionFactoryDefinition = { + id: 'test-factory-1', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: (() => true) as any, + create: () => ({} as any), + }; + const factoryDefinition2: ActionFactoryDefinition = { + id: 'test-factory-2', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: (() => true) as any, + create: () => ({} as any), + }; + + test('.getActionFactories() returns empty array if no action factories registered', () => { + const service = new UiActionsService(); + + const factories = service.getActionFactories(); + + expect(factories).toEqual([]); + }); + + test('can register and retrieve an action factory', () => { + const service = new UiActionsService(); + + service.registerActionFactory(factoryDefinition1); + + const factory = service.getActionFactory(factoryDefinition1.id); + + expect(factory).toBeInstanceOf(ActionFactory); + expect(factory.id).toBe(factoryDefinition1.id); + }); + + test('can retrieve all action factories', () => { + const service = new UiActionsService(); + + service.registerActionFactory(factoryDefinition1); + service.registerActionFactory(factoryDefinition2); + + const factories = service.getActionFactories(); + const factoriesSorted = [...factories].sort((f1, f2) => (f1.id > f2.id ? 1 : -1)); + + expect(factoriesSorted.length).toBe(2); + expect(factoriesSorted[0].id).toBe(factoryDefinition1.id); + expect(factoriesSorted[1].id).toBe(factoryDefinition2.id); + }); + + test('throws when retrieving action factory that does not exist', () => { + const service = new UiActionsService(); + + service.registerActionFactory(factoryDefinition1); + + expect(() => service.getActionFactory('UNKNOWN_ID')).toThrowError( + 'Action factory [actionFactoryId = UNKNOWN_ID] does not exist.' + ); + }); }); }); diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index da91e4fd909f6..7c77ec0dcfc35 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -110,76 +110,22 @@ export class UiActionsService { this.actions.delete(actionId); }; - public readonly addTriggerAction = ( - triggerId: TriggerId, - definition: ActionDefinition - ) => { - // Check if trigger exists, if not, next line throws. - this.getTrigger(triggerId); - - const action = this.registerAction(definition); - this.__attachAction(triggerId, action.id); - - return action; - }; - - public readonly removeTriggerAction = ( - triggerId: TriggerId, - actionId: string - ) => { - this.detachAction(triggerId, actionId); - this.unregisterAction(actionId); - }; - - // public readonly removeTriggerAction = - - public readonly __attachAction = ( + public readonly attachAction = ( triggerId: TriggerId, actionId: string ): void => { - const actionIds = this.triggerToActions.get(triggerId); - - if (!actionIds!.find(id => id === actionId)) { - this.triggerToActions.set(triggerId, [...actionIds!, actionId]); - } - }; - - public readonly getAction = (id: string): ActionInternal => { - if (!this.actions.has(id)) { - throw new Error(`Action [action.id = ${id}] not registered.`); - } - - return this.actions.get(id) as ActionInternal; - }; - - public readonly attachAction = ( - triggerId: TType, - // The action can accept partial or no context, but if it needs context not provided - // by this type of trigger, typescript will complain. yay! - action: ActionByType & Action - ): void => { - if (!this.actions.has(action.id)) { - this.registerAction(action); - } else { - const registeredAction = this.actions.get(action.id); - // todo - verify this - if (registeredAction!.id !== action.id) { - throw new Error(`A different action instance with this id is already registered.`); - } - } - const trigger = this.triggers.get(triggerId); if (!trigger) { throw new Error( - `No trigger [triggerId = ${triggerId}] exists, for attaching action [actionId = ${action.id}].` + `No trigger [triggerId = ${triggerId}] exists, for attaching action [actionId = ${actionId}].` ); } const actionIds = this.triggerToActions.get(triggerId); - if (!actionIds!.find(id => id === action.id)) { - this.triggerToActions.set(triggerId, [...actionIds!, action.id]); + if (!actionIds!.find(id => id === actionId)) { + this.triggerToActions.set(triggerId, [...actionIds!, actionId]); } }; @@ -200,6 +146,24 @@ export class UiActionsService { ); }; + public readonly addTriggerAction = ( + triggerId: TType, + // The action can accept partial or no context, but if it needs context not provided + // by this type of trigger, typescript will complain. yay! + action: ActionByType & Action + ): void => { + if (!this.actions.has(action.id)) this.registerAction(action); + this.attachAction(triggerId, action.id); + }; + + public readonly getAction = (id: string): ActionInternal => { + if (!this.actions.has(id)) { + throw new Error(`Action [action.id = ${id}] not registered.`); + } + + return this.actions.get(id) as ActionInternal; + }; + public readonly getTriggerActions = ( triggerId: T ): Array> => { diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts index 5b427f918c173..ade21ee4b7d91 100644 --- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts @@ -69,7 +69,7 @@ test('executes a single action mapped to a trigger', async () => { const action = createTestAction('test1', () => true); setup.registerTrigger(trigger); - setup.attachAction(trigger.id, action); + setup.addTriggerAction(trigger.id, action); const context = {}; const start = doStart(); @@ -109,7 +109,7 @@ test('does not execute an incompatible action', async () => { ); setup.registerTrigger(trigger); - setup.attachAction(trigger.id, action); + setup.addTriggerAction(trigger.id, action); const start = doStart(); const context = { @@ -130,8 +130,8 @@ test('shows a context menu when more than one action is mapped to a trigger', as const action2 = createTestAction('test2', () => true); setup.registerTrigger(trigger); - setup.attachAction(trigger.id, action1); - setup.attachAction(trigger.id, action2); + setup.addTriggerAction(trigger.id, action1); + setup.addTriggerAction(trigger.id, action2); expect(openContextMenu).toHaveBeenCalledTimes(0); @@ -155,7 +155,7 @@ test('passes whole action context to isCompatible()', async () => { }); setup.registerTrigger(trigger); - setup.attachAction(trigger.id, action); + setup.addTriggerAction(trigger.id, action); const start = doStart(); diff --git a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts index 53960c2d47266..55ccac42ff255 100644 --- a/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/get_trigger_actions.test.ts @@ -47,14 +47,14 @@ test('returns actions set on trigger', () => { expect(list0).toHaveLength(0); - setup.attachAction('trigger' as TriggerId, action1); + setup.addTriggerAction('trigger' as TriggerId, action1); const list1 = start.getTriggerActions('trigger' as TriggerId); expect(list1).toHaveLength(1); expect(list1[0]).toBeInstanceOf(ActionInternal); expect(list1[0].id).toBe(action1.id); - setup.attachAction('trigger' as TriggerId, action2); + setup.addTriggerAction('trigger' as TriggerId, action2); const list2 = start.getTriggerActions('trigger' as TriggerId); expect(list2).toHaveLength(2); diff --git a/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts b/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts index c5e68e5d5ca5a..21dd17ed82e3f 100644 --- a/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/get_trigger_compatible_actions.test.ts @@ -37,7 +37,7 @@ beforeEach(() => { id: 'trigger' as TriggerId, title: 'trigger', }); - uiActions.setup.attachAction('trigger' as TriggerId, action); + uiActions.setup.addTriggerAction('trigger' as TriggerId, action); }); test('can register action', async () => { @@ -58,7 +58,7 @@ test('getTriggerCompatibleActions returns attached actions', async () => { title: 'My trigger', }; setup.registerTrigger(testTrigger); - setup.attachAction('MY-TRIGGER' as TriggerId, helloWorldAction); + setup.addTriggerAction('MY-TRIGGER' as TriggerId, helloWorldAction); const start = doStart(); const actions = await start.getTriggerCompatibleActions('MY-TRIGGER' as TriggerId, {}); @@ -84,7 +84,7 @@ test('filters out actions not applicable based on the context', async () => { setup.registerTrigger(testTrigger); setup.registerAction(action1); - setup.attachAction(testTrigger.id, action1); + setup.addTriggerAction(testTrigger.id, action1); const start = doStart(); let actions = await start.getTriggerCompatibleActions(testTrigger.id, { accept: true }); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 18ceec652392d..8ddb2e1a4803b 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -70,11 +70,10 @@ export class EmbeddableExplorerPublicPlugin const sayHelloAction = new SayHelloAction(alert); const sendMessageAction = createSendMessageAction(core.overlays); - plugins.uiActions.registerAction(helloWorldAction); plugins.uiActions.registerAction(sayHelloAction); plugins.uiActions.registerAction(sendMessageAction); - plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, helloWorldAction); + plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, helloWorldAction); plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx index 8395fddece2a4..7c7cc689d05e5 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx @@ -62,5 +62,4 @@ function createSamplePanelAction() { } const action = createSamplePanelAction(); -npSetup.plugins.uiActions.registerAction(action); -npSetup.plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); +npSetup.plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts index 4b09be4db8a60..e034fbe320608 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_link.ts @@ -33,5 +33,4 @@ export const createSamplePanelLink = (): Action => }); const action = createSamplePanelLink(); -npStart.plugins.uiActions.registerAction(action); -npStart.plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); +npStart.plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 133733657b8af..71ea72499d87f 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -72,16 +72,14 @@ export class AdvancedUiActionsPublicPlugin dateFormat, commonlyUsedRanges, }); - uiActions.registerAction(timeRangeAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, timeRangeAction); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, timeRangeAction); const timeRangeBadge = new CustomTimeRangeBadge({ openModal, dateFormat, commonlyUsedRanges, }); - uiActions.registerAction(timeRangeBadge); - uiActions.attachAction(PANEL_BADGE_TRIGGER, timeRangeBadge); + uiActions.addTriggerAction(PANEL_BADGE_TRIGGER, timeRangeBadge); return { ...uiActions, diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index a8554d9e2339f..82e88bb6efeb5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -36,12 +36,10 @@ export class DashboardDrilldownsService { const savedObjects = async () => (await core.getStartServices())[0].savedObjects.client; const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.registerAction(actionFlyoutCreateDrilldown); - plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); + plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.registerAction(actionFlyoutEditDrilldown); - plugins.uiActions.attachAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); + plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ savedObjects, diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index 08ba10ff69207..ac46d84469513 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -143,8 +143,7 @@ export class ReportingPublicPlugin implements Plugin { }, }); - uiActions.registerAction(action); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, action); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); share.register(csvReportingProvider({ apiClient, toasts, license$ })); share.register( From 469250f97bef2272fbf9cd54d7d3908d9e9f7d11 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 19 Mar 2020 10:09:49 +0100 Subject: [PATCH 48/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20don't=20show=20"OP?= =?UTF-8?q?TIONS"=20title=20on=20drilldown=20context=20menus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../context_menu/build_eui_context_menu_panels.tsx | 10 +++++++--- .../ui_actions/public/triggers/select_range_trigger.ts | 2 +- .../ui_actions/public/triggers/trigger_internal.ts | 1 + .../ui_actions/public/triggers/value_click_trigger.ts | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx index 05c8c1c0f3089..ec58261d9e4f7 100644 --- a/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx +++ b/src/plugins/ui_actions/public/context_menu/build_eui_context_menu_panels.tsx @@ -24,16 +24,22 @@ import { i18n } from '@kbn/i18n'; import { uiToReactComponent } from '../../../kibana_react/public'; import { Action } from '../actions'; +export const defaultTitle = i18n.translate('uiActions.actionPanel.title', { + defaultMessage: 'Options', +}); + /** * Transforms an array of Actions to the shape EuiContextMenuPanel expects. */ export async function buildContextMenuForActions({ actions, actionContext, + title = defaultTitle, closeMenu, }: { actions: Array>; actionContext: Context; + title?: string; closeMenu: () => void; }): Promise { const menuItems = await buildEuiContextMenuPanelItems({ @@ -44,9 +50,7 @@ export async function buildContextMenuForActions({ return { id: 'mainMenu', - title: i18n.translate('uiActions.actionPanel.title', { - defaultMessage: 'Options', - }), + title, items: menuItems, }; } diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts index c638db0ce9dab..9758508dc3dac 100644 --- a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts @@ -22,6 +22,6 @@ import { Trigger } from '.'; export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { id: SELECT_RANGE_TRIGGER, - title: 'Select range', + title: '', description: 'Applies a range filter', }; diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index 5b670df354f78..9885ed3abe93b 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -72,6 +72,7 @@ export class TriggerInternal { const panel = await buildContextMenuForActions({ actions, actionContext: context, + title: this.trigger.title, closeMenu: () => session.close(), }); const session = openContextMenu([panel]); diff --git a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts index ad32bdc1b564e..2671584d105c8 100644 --- a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts @@ -22,6 +22,6 @@ import { Trigger } from '.'; export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { id: VALUE_CLICK_TRIGGER, - title: 'Value clicked', + title: '', description: 'Value was clicked', }; From c73319a2249a7eca50447e1bd38f34d28595dc33 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 19 Mar 2020 13:03:10 +0100 Subject: [PATCH 49/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20server-side?= =?UTF-8?q?=20for=20x-pack=20dashboard=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/dashboard_enhanced/kibana.json | 2 +- .../dashboard_enhanced/server/config.ts | 23 +++++++++++++++++++ .../dashboard_enhanced/server/index.ts | 10 ++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/dashboard_enhanced/server/config.ts create mode 100644 x-pack/plugins/dashboard_enhanced/server/index.ts diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index a9b6920ae369e..8d51714ae440c 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -1,7 +1,7 @@ { "id": "dashboardEnhanced", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": ["uiActions", "embeddable", "drilldowns"] } diff --git a/x-pack/plugins/dashboard_enhanced/server/config.ts b/x-pack/plugins/dashboard_enhanced/server/config.ts new file mode 100644 index 0000000000000..b75c95d5f8832 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/server/config.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '../../../../src/core/server'; + +export const configSchema = schema.object({ + drilldowns: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), +}); + +export type ConfigSchema = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + drilldowns: true, + }, +}; diff --git a/x-pack/plugins/dashboard_enhanced/server/index.ts b/x-pack/plugins/dashboard_enhanced/server/index.ts new file mode 100644 index 0000000000000..05bdc3b3113e5 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/server/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export const plugin = () => ({ + setup() {}, + start() {}, +}); From 2280fdf798b5035bdf4c9c45f8dfd1e57957380f Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 19 Mar 2020 13:07:57 +0100 Subject: [PATCH 50/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20disable=20Drilldow?= =?UTF-8?q?ns=20for=20TSVB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/np_ready/public/embeddable/visualize_embeddable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 1567f904e951a..7f2ad2de1c4a8 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -406,7 +406,6 @@ export class VisualizeEmbeddable extends Embeddable Date: Thu, 19 Mar 2020 13:42:01 +0100 Subject: [PATCH 51/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20enable=20drilldown?= =?UTF-8?q?s=20on=20kibana.yml=20feature=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/dashboard_enhanced/kibana.json | 3 ++- .../plugins/dashboard_enhanced/public/index.ts | 5 +++-- .../plugins/dashboard_enhanced/public/plugin.ts | 11 +++++++++-- .../drilldowns/dashboard_drilldowns_services.ts | 16 +++++++++++++++- .../plugins/dashboard_enhanced/server/index.ts | 2 ++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index 8d51714ae440c..95d6eb044ac3b 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "drilldowns"] + "requiredPlugins": ["uiActions", "embeddable", "drilldowns"], + "configPath": ["xpack", "dashboardEnhanced"] } diff --git a/x-pack/plugins/dashboard_enhanced/public/index.ts b/x-pack/plugins/dashboard_enhanced/public/index.ts index 8f48bae9f3803..53540a4a1ad2e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializerContext } from 'src/core/public'; import { DashboardEnhancedPlugin } from './plugin'; export { @@ -13,6 +14,6 @@ export { StartDependencies as DashboardEnhancedStartDependencies, } from './plugin'; -export function plugin() { - return new DashboardEnhancedPlugin(); +export function plugin(context: PluginInitializerContext) { + return new DashboardEnhancedPlugin(context); } diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 447ceff4b8cb2..853b3e7b2ffcd 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart, CoreSetup, Plugin } from 'src/core/public'; +import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DashboardDrilldownsService } from './services'; import { DrilldownsSetupContract, DrilldownsStartContract } from '../../drilldowns/public'; @@ -28,9 +28,16 @@ export interface StartContract {} export class DashboardEnhancedPlugin implements Plugin { public readonly drilldowns = new DashboardDrilldownsService(); + public readonly config: { drilldowns: { enabled: boolean } }; + + constructor(protected readonly context: PluginInitializerContext) { + this.config = context.config.get(); + } public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { - this.drilldowns.bootstrap(core, plugins); + this.drilldowns.bootstrap(core, plugins, { + enableDrilldowns: this.config.drilldowns.enabled, + }); return {}; } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 82e88bb6efeb5..a353eed0964bb 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -26,8 +26,22 @@ declare module '../../../../../../src/plugins/ui_actions/public' { } } +interface BootstrapParams { + enableDrilldowns: boolean; +} + export class DashboardDrilldownsService { - async bootstrap( + bootstrap( + core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, + plugins: SetupDependencies, + { enableDrilldowns }: BootstrapParams + ) { + if (enableDrilldowns) { + this.setupDrilldowns(core, plugins); + } + } + + setupDrilldowns( core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, plugins: SetupDependencies ) { diff --git a/x-pack/plugins/dashboard_enhanced/server/index.ts b/x-pack/plugins/dashboard_enhanced/server/index.ts index 05bdc3b3113e5..e361b9fb075ed 100644 --- a/x-pack/plugins/dashboard_enhanced/server/index.ts +++ b/x-pack/plugins/dashboard_enhanced/server/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export { config } from './config'; + export const plugin = () => ({ setup() {}, start() {}, From 9e22e55bb84617bff256f5c4954aac6586a0bced Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 19 Mar 2020 15:38:40 +0100 Subject: [PATCH 52/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20feature=20fl?= =?UTF-8?q?ag=20comment=20to=20kibana.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/kibana.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/kibana.yml b/config/kibana.yml index 0780841ca057e..7b11a32bbdb42 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -110,3 +110,6 @@ # Specifies locale to be used for all localizable strings, dates and number formats. # Supported languages are the following: English - en , by default , Chinese - zh-CN . #i18n.locale: "en" + +# Enables "Drilldowns" functionality on dashboard. Set to false by default. +# xpack.dashboardEnhanced.drilldowns.enabled: false From ed8580a9c42290a4e81965212646208d960494b0 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 09:57:13 +0100 Subject: [PATCH 53/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20remove=20places=20?= =?UTF-8?q?from=20drilldown=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard_to_dashboard_drilldown/drilldown.tsx | 2 -- .../plugins/drilldowns/public/services/drilldown_service.ts | 3 +-- x-pack/plugins/drilldowns/public/types.ts | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index e80f4a24a56de..c5c61c21257fc 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -21,8 +21,6 @@ export class DashboardToDashboardDrilldown implements Drilldown { constructor(protected readonly params: Params) {} - // TODO: public readonly places = ['dashboard']; - public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; public readonly order = 100; diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index 3f89a9f5d6441..8fd4b622cc2b3 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -37,7 +37,6 @@ export class DrilldownService { ExecutionContext extends object = object >({ id: factoryId, - places, CollectConfig, createConfig, isConfigValid, @@ -56,7 +55,7 @@ export class DrilldownService { isConfigValid, getDisplayName, getIconType: () => euiIcon, - isCompatible: async ({ place }: any) => (!places ? true : places.indexOf(place) > -1), + isCompatible: async () => true, create: serializedAction => ({ id: '', type: factoryId, diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index f34aa89ddd211..7d34151b33084 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -16,12 +16,6 @@ export interface Drilldown< */ id: string; - /** - * List of places where this drilldown should be available, e.g "dashboard", "graph". - * If omitted, the drilldown will be shown in all places. - */ - places?: string[]; - /** * Function that returns default config for this drilldown. */ From 73a91f3f6452ec42cba61cfb7f7f248254da622e Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 10:13:31 +0100 Subject: [PATCH 54/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20place?= =?UTF-8?q?=20in=20factory=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connected_flyout_manage_drilldowns.tsx | 1 - x-pack/plugins/drilldowns/public/types.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 2a7e2f28cb991..e3f793bea81a1 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -76,7 +76,6 @@ export function createFlyoutManageDrilldowns({ const factoryContext: DrilldownFactoryContext = React.useMemo( () => ({ - place: '', placeContext: props.context, triggers: [], }), diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 7d34151b33084..c04145767338a 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -101,11 +101,6 @@ export interface Drilldown< * Context object used when creating a drilldown. */ export interface DrilldownFactoryContext { - /** - * Place where factory is being rendered. - */ - place?: string; - /** * Context provided to the drilldown factory by the place where the UI is * rendered. For example, for the "dashboard" place, this context contains From 4d500b59a2a35ec8bb1862e76bec3fe6d3a55b4a Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 10:17:48 +0100 Subject: [PATCH 55/79] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20doExecut?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/drilldowns/public/types.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index c04145767338a..8e4f8e2cbfbab 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -71,20 +71,6 @@ export interface Drilldown< */ getDisplayName: () => string; - /** - * Whether this drilldown should be considered for execution given `config` - * and `context`. When multiple drilldowns are attached to the same trigger - * user is presented with a context menu to pick one drilldown for execute. If - * this method returns `true` this trigger will appear in the context menu - * list, if `false`, it will not be presented to the user. If `doExecute` is - * not implemented, this drilldown will always be show to the user. - * - * @param config Config object that user configured this drilldown with. - * @param context Object that represents context in which the underlying - * `UIAction` of this drilldown is being executed in. - */ - doExecute?(config: Config, context: ExecutionContext): Promise; - /** * Implements the "navigation" action of the drilldown. This happens when * user clicks something in the UI that executes a trigger to which this From 054fc8f31f2d089d62e30feabdb6d7c3921cdf54 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 20 Mar 2020 10:23:04 +0100 Subject: [PATCH 56/79] remove not needed now error_configure_action component --- .../action_identifier.tsx | 43 ------------------- .../error_configure_action.story.tsx | 34 --------------- .../error_configure_action.tsx | 40 ----------------- .../components/error_configure_action/i18n.ts | 27 ------------ .../error_configure_action/index.tsx | 20 --------- 5 files changed, 164 deletions(-) delete mode 100644 src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx delete mode 100644 src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx delete mode 100644 src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx delete mode 100644 src/plugins/ui_actions/public/components/error_configure_action/i18n.ts delete mode 100644 src/plugins/ui_actions/public/components/error_configure_action/index.tsx diff --git a/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx b/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx deleted file mode 100644 index 969e3dbac0e67..0000000000000 --- a/src/plugins/ui_actions/public/components/error_configure_action/action_identifier.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiCode } from '@elastic/eui'; -import { ActionInternal } from '../../actions'; - -export interface ActionIdentifierProps { - action: ActionInternal; -} - -export const ActionIdentifier: React.FC = ({ action }) => ( -

- {action.id && ( - <> - Action ID: {action.id} -
- - )} - {action.type && ( - <> - Action type: {action.type} -
- - )} -

-); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx deleted file mode 100644 index 655302bf54734..0000000000000 --- a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.story.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { ErrorConfigureAction } from '.'; -import { ActionInternal } from '../../actions'; - -const action = new ActionInternal({ - id: 'TEST', - type: 'TEST_TYPE' as any, - execute: async () => {}, -}); - -storiesOf('components/ErrorConfigureAction', module) - .add('default', () => ) - .add('with action', () => ) - .add('with action and message', () => ); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx b/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx deleted file mode 100644 index 93ef139cf1940..0000000000000 --- a/src/plugins/ui_actions/public/components/error_configure_action/error_configure_action.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; -import { txtSorryActionConfigurationError } from './i18n'; -import { ActionIdentifier } from './action_identifier'; -import { ActionInternal } from '../../actions'; - -export interface ErrorConfigureActionProps { - msg?: React.ReactNode; - action?: ActionInternal; -} - -export const ErrorConfigureAction: React.FC = ({ - action, - msg, - children, -}) => ( - - {(msg || children) &&

{msg || children}

} - {action && } -
-); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts b/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts deleted file mode 100644 index ec0c3c533b20a..0000000000000 --- a/src/plugins/ui_actions/public/components/error_configure_action/i18n.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; - -export const txtSorryActionConfigurationError = i18n.translate( - 'uiActions.components.sorryActionConfigurationError', - { - defaultMessage: 'Sorry, action configuration error', - } -); diff --git a/src/plugins/ui_actions/public/components/error_configure_action/index.tsx b/src/plugins/ui_actions/public/components/error_configure_action/index.tsx deleted file mode 100644 index a38fe0fb8e190..0000000000000 --- a/src/plugins/ui_actions/public/components/error_configure_action/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './error_configure_action'; From 1e9efde4ad4d1d8b613966e4adc2ae65a67072f1 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 20 Mar 2020 10:23:18 +0100 Subject: [PATCH 57/79] remove workaround for storybook --- packages/kbn-storybook/storybook_config/webpack.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/kbn-storybook/storybook_config/webpack.config.js b/packages/kbn-storybook/storybook_config/webpack.config.js index 5b6897e58a8e9..1531c1d22b01b 100644 --- a/packages/kbn-storybook/storybook_config/webpack.config.js +++ b/packages/kbn-storybook/storybook_config/webpack.config.js @@ -66,8 +66,7 @@ module.exports = async ({ config }) => { config.module.rules.push({ test: /\.tsx$/, // Exclude example files, as we don't display props info for them - // TODO: validated_dual_range throws: https://github.com/elastic/kibana/issues/60356 - exclude: /\.examples.tsx$|validated_dual_range.tsx$/, + exclude: /\.examples.tsx$/, use: [ // Parse TS comments to create Props tables in the UI require.resolve('react-docgen-typescript-loader'), From 1a3e67baf09f0635f2a1194e5948fda2ec765450 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 10:45:23 +0100 Subject: [PATCH 58/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20Drilldow?= =?UTF-8?q?nDefinition=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drilldown.tsx | 2 +- .../connected_flyout_manage_drilldowns.tsx | 4 +-- x-pack/plugins/drilldowns/public/index.ts | 2 +- .../public/services/drilldown_service.ts | 6 ++-- x-pack/plugins/drilldowns/public/types.ts | 28 +++++++++++++++---- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index c5c61c21257fc..8b8b012f888d5 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -10,7 +10,7 @@ import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_reac import { FactoryContext, ActionContext, Config, CollectConfigProps } from './types'; import { CollectConfigContainer } from './collect_config'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; -import { DrilldownsDrilldown as Drilldown } from '../../../../../drilldowns/public'; +import { DrilldownDefinition as Drilldown } from '../../../../../drilldowns/public'; import { txtGoToDashboard } from './i18n'; export interface Params { diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index e3f793bea81a1..38660a72ab65c 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -77,9 +77,9 @@ export function createFlyoutManageDrilldowns({ const factoryContext: DrilldownFactoryContext = React.useMemo( () => ({ placeContext: props.context, - triggers: [], + triggers: selectedTriggers, }), - [props.context] + [props.context, selectedTriggers] ); const actionFactories = useCompatibleActionFactoriesForCurrentContext( diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 6da0b96ad631c..43f17b2a64304 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -17,4 +17,4 @@ export function plugin() { return new DrilldownsPlugin(); } -export { Drilldown as DrilldownsDrilldown } from './types'; +export { DrilldownDefinition } from './types'; diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index 8fd4b622cc2b3..af53656858b33 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -6,7 +6,7 @@ import { CoreSetup } from 'src/core/public'; import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; -import { Drilldown, DrilldownFactoryContext } from '../types'; +import { DrilldownDefinition, DrilldownFactoryContext } from '../types'; import { UiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../../../../src/plugins/ui_actions/public'; export interface DrilldownServiceSetupDeps { @@ -22,7 +22,7 @@ export interface DrilldownServiceSetupContract { CreationContext extends object = object, ExecutionContext extends object = object >( - drilldown: Drilldown + drilldown: DrilldownDefinition ) => void; } @@ -43,7 +43,7 @@ export class DrilldownService { getDisplayName, euiIcon, execute, - }: Drilldown) => { + }: DrilldownDefinition) => { const actionFactory: ActionFactoryDefinition< Config, DrilldownFactoryContext, diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 8e4f8e2cbfbab..a8232887f9ca6 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -6,9 +6,27 @@ import { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../advanced_ui_actions/public'; -export interface Drilldown< +/** + * This is a convenience interface to register a drilldown. Drilldown has + * ability to collect configuration from user. Once drilldown is executed it + * receives the collected information together with the context of the + * user's interaction. + * + * `Config` is a serializable object containing the configuration that the + * drilldown is able to collect using UI. + * + * `PlaceContext` is an object that the app that opens drilldown management + * flyout provides to the React component, specifying the contextual information + * about that app. For example, on Dashboard app this context contains + * information about the current embeddable and dashboard. + * + * `ExecutionContext` is an object created in response to user's interaction + * and provided to the `execute` function of the drilldown. This object contains + * information about the action user performed. + */ +export interface DrilldownDefinition< Config extends object = object, - CreationContext extends object = object, + PlaceContext extends object = object, ExecutionContext extends object = object > { /** @@ -21,7 +39,7 @@ export interface Drilldown< */ createConfig: ActionFactoryDefinition< Config, - DrilldownFactoryContext, + DrilldownFactoryContext, ExecutionContext >['createConfig']; @@ -46,7 +64,7 @@ export interface Drilldown< */ CollectConfig: ActionFactoryDefinition< Config, - DrilldownFactoryContext, + DrilldownFactoryContext, ExecutionContext >['CollectConfig']; @@ -56,7 +74,7 @@ export interface Drilldown< */ isConfigValid: ActionFactoryDefinition< Config, - DrilldownFactoryContext, + DrilldownFactoryContext, ExecutionContext >['isConfigValid']; From d1666ca0de0db87d120e64e5f98e8c9938fecc71 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 11:09:09 +0100 Subject: [PATCH 59/79] =?UTF-8?q?style:=20=F0=9F=92=84=20replace=20any=20b?= =?UTF-8?q?y=20unknown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/lib/embeddables/embeddable.tsx | 4 +++- .../ui_actions/public/actions/dynamic_action_manager.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 90a6e16faeed5..b08eb4f6dd596 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -27,6 +27,7 @@ import { UiActionsDynamicActionManager, UiActionsStart, } from '../../../../../plugins/ui_actions/public'; +import { EmbeddableContext } from '../triggers'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; @@ -71,7 +72,8 @@ export abstract class Embeddable< if (!this.params.uiActions) return undefined; if (!this.cachedDynamicActions) { this.cachedDynamicActions = new UiActionsDynamicActionManager({ - isCompatible: async ({ embeddable }: any) => embeddable.runtimeId === this.runtimeId, + isCompatible: async (context: unknown) => + (context as EmbeddableContext).embeddable.runtimeId === this.runtimeId, storage: this.storage, uiActions: this.params.uiActions, }); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 6e2d0ebe3aae9..7ca1e62310e58 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -142,7 +142,7 @@ export class DynamicActionManager { * 1. Loads all events from @type {DynamicActionStorage} storage. * 2. Creates actions for each event in `ui_actions` registry. * 3. Adds events to UI state. - * 4. Does nothing if dynamic action manager was stopped of if event fetching + * 4. Does nothing if dynamic action manager was stopped or if event fetching * is already taking place. */ public async start() { From 32466a51b44f2905cf360ca1652d4076e8881eed Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 11:25:14 +0100 Subject: [PATCH 60/79] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20any?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/components/action_wizard/test_data.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 3787c1651a586..4048a22ff4849 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -146,7 +146,7 @@ export const urlDrilldownActionFactory: ActionFactoryDefinition { + isConfigValid: (config: UrlDrilldownConfig): config is UrlDrilldownConfig => { if (!config.url) return false; return true; }, From df1092054d4627f81bb879c884cc13d75d77449b Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 11:57:31 +0100 Subject: [PATCH 61/79] =?UTF-8?q?chore:=20=F0=9F=A4=96=20make=20isConfigVa?= =?UTF-8?q?lid=20return=20type=20a=20boolean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/util/configurable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index fe235d4b5daac..d3a527a2183b1 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -31,7 +31,7 @@ export interface Configurable /** * Is this config valid. Used to validate user's input before saving. */ - readonly isConfigValid: (config: Config) => config is Config; + readonly isConfigValid: (config: Config) => boolean; /** * `UiComponent` to be rendered when collecting configuration for this item. From d480655ad00bc73e5f8e8f9cea0a1372d1d1801b Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 12:32:58 +0100 Subject: [PATCH 62/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20getDisp?= =?UTF-8?q?layName=20to=20factory,=20remove=20deprecated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/actions/dynamic_action_manager.ts | 20 ------------------- .../public/service/ui_actions_service.test.ts | 6 +++--- .../public/services/drilldown_service.ts | 1 + 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 7ca1e62310e58..5f6ee86a56c3e 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -78,7 +78,6 @@ export class DynamicActionManager { protected reviveAction(event: SerializedEvent) { const { eventId, triggers, action } = event; const { uiActions, isCompatible } = this.params; - const { name } = action; const actionId = this.generateActionId(eventId); const factory = uiActions.getActionFactory(event.action.factoryId); @@ -86,7 +85,6 @@ export class DynamicActionManager { ...factory.create(action as SerializedAction), id: actionId, isCompatible, - getDisplayName: () => name, }; uiActions.registerAction(actionDefinition); @@ -283,22 +281,4 @@ export class DynamicActionManager { public async deleteEvents(eventIds: string[]) { await Promise.all(eventIds.map(this.deleteEvent.bind(this))); } - - /** - * @deprecated - * - * Use `.state.get().events` instead. - */ - public async list(): Promise { - return this.state.get().events; - } - - /** - * @deprecated - * - * Use `.state.get().events.length` instead. - */ - public async count(): Promise { - return this.state.get().events.length; - } } diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts index 18262f0945cf8..41e2b57d53dd8 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.test.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.test.ts @@ -186,7 +186,7 @@ describe('UiActionsService', () => { service.registerAction(helloWorldAction); expect(actions.size - length).toBe(1); - expect((actions.get(helloWorldAction.id) as any).id).toBe(helloWorldAction.id); + expect(actions.get(helloWorldAction.id)!.id).toBe(helloWorldAction.id); }); test('getTriggerCompatibleActions returns attached actions', async () => { @@ -503,14 +503,14 @@ describe('UiActionsService', () => { id: 'test-factory-1', CollectConfig: {} as any, createConfig: () => ({}), - isConfigValid: (() => true) as any, + isConfigValid: () => true, create: () => ({} as any), }; const factoryDefinition2: ActionFactoryDefinition = { id: 'test-factory-2', CollectConfig: {} as any, createConfig: () => ({}), - isConfigValid: (() => true) as any, + isConfigValid: () => true, create: () => ({} as any), }; diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index af53656858b33..bfbe514d46095 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -60,6 +60,7 @@ export class DrilldownService { id: '', type: factoryId, getIconType: () => euiIcon, + getDisplayName: () => serializedAction.name, execute: async context => await execute(serializedAction.config, context), }), } as ActionFactoryDefinition< From eb24e411d2a9b4b32677f103006d14b4bbe90c9d Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 12:46:17 +0100 Subject: [PATCH 63/79] =?UTF-8?q?style:=20=F0=9F=92=84=20remove=20any?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/lib/embeddables/embeddable_action_storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts index b224a67144df4..fad5b4d535d6c 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts @@ -24,7 +24,7 @@ import { import { Embeddable } from '..'; export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { - constructor(private readonly embbeddable: Embeddable) { + constructor(private readonly embbeddable: Embeddable) { super(); } From 930b6958a07f8c54937b85f08eebb0223df12e6a Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 12:51:38 +0100 Subject: [PATCH 64/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20ActionFa?= =?UTF-8?q?ctoryDefinition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui_actions/public/actions/action_factory_definition.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts index 8cc9215061e19..7ac94a41e7076 100644 --- a/src/plugins/ui_actions/public/actions/action_factory_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_factory_definition.ts @@ -28,10 +28,10 @@ export interface ActionFactoryDefinition< Config extends object = object, FactoryContext extends object = object, ActionContext extends object = object -> extends Partial>, Configurable { +> extends Partial>, Configurable { /** * Unique ID of the action factory. This ID is used to identify this action - * factory in the registry as well as to construct actions of this ID and + * factory in the registry as well as to construct actions of this type and * identify this action factory when presenting it to the user in UI. */ id: string; From 6a98f45c9bd8bd9f87df6edd7468146d9e2dc278 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 13:19:22 +0100 Subject: [PATCH 65/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20visua?= =?UTF-8?q?lize=5Fembeddable=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/embeddable/visualize_embeddable_factory.tsx | 6 ++---- .../visualizations/public/np_ready/public/plugin.ts | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx index b2abe67dcc608..1ee522697fff8 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx @@ -54,9 +54,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< public readonly type = VISUALIZE_EMBEDDABLE_TYPE; constructor( - private readonly getServices: () => Promise< - [unknown, Pick] - > + private readonly getServices: () => Promise> ) { super({ savedObjectMetaData: { @@ -119,7 +117,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const indexPattern = await getIndexPattern(savedObject); const indexPatterns = indexPattern ? [indexPattern] : []; - const [, { uiActions }] = await this.getServices(); + const { uiActions } = await this.getServices(); const editable = await this.isEditable(); return new VisualizeEmbeddable( diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index a553eea5b6f0b..484c9e0b2421a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -114,7 +114,9 @@ export class VisualizationsPlugin expressions.registerFunction(visualizationFunction); expressions.registerRenderer(visualizationRenderer); - const embeddableFactory = new VisualizeEmbeddableFactory(core.getStartServices); + const embeddableFactory = new VisualizeEmbeddableFactory( + async () => (await core.getStartServices())[1] + ); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); return { From da490526c5f8aff9b319179d7583929d1a241b57 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 13:30:56 +0100 Subject: [PATCH 66/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20dashboard=20?= =?UTF-8?q?dependency=20to=20dashboard=5Fenhanced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/dashboard_enhanced/kibana.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index 95d6eb044ac3b..acbca5c33295c 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "drilldowns"], + "requiredPlugins": ["uiActions", "embeddable", "dashboard", "drilldowns"], "configPath": ["xpack", "dashboardEnhanced"] } From baf0f5e1f6941790b91ae6cd440d308cac368d37 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 14:33:25 +0100 Subject: [PATCH 67/79] =?UTF-8?q?style:=20=F0=9F=92=84=20rename=20drilldow?= =?UTF-8?q?n=20plugin=20life-cycle=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/dashboard_enhanced/public/plugin.ts | 6 +++--- .../flyout_create_drilldown/flyout_create_drilldown.tsx | 4 ++-- .../flyout_edit_drilldown/flyout_edit_drilldown.tsx | 4 ++-- .../services/drilldowns/dashboard_drilldowns_services.ts | 9 +++------ x-pack/plugins/drilldowns/public/index.ts | 4 ++-- x-pack/plugins/drilldowns/public/mocks.ts | 6 +++--- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 853b3e7b2ffcd..30b3f3c080f49 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -7,16 +7,16 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DashboardDrilldownsService } from './services'; -import { DrilldownsSetupContract, DrilldownsStartContract } from '../../drilldowns/public'; +import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { uiActions: UiActionsSetup; - drilldowns: DrilldownsSetupContract; + drilldowns: DrilldownsSetup; } export interface StartDependencies { uiActions: UiActionsStart; - drilldowns: DrilldownsStartContract; + drilldowns: DrilldownsStart; } // eslint-disable-next-line diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 3b4f536006cde..792238adff313 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; -import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; +import { DrilldownsStart } from '../../../../../../drilldowns/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; export interface OpenFlyoutAddDrilldownParams { overlays: () => Promise; - drilldowns: () => Promise; + drilldowns: () => Promise; } export class FlyoutCreateDrilldownAction implements ActionByType { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 91be24ab0087c..92de8f4335add 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -12,7 +12,7 @@ import { toMountPoint, } from '../../../../../../../../src/plugins/kibana_react/public'; import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; -import { DrilldownsStartContract } from '../../../../../../drilldowns/public'; +import { DrilldownsStart } from '../../../../../../drilldowns/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; @@ -20,7 +20,7 @@ export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; export interface FlyoutEditDrilldownParams { overlays: () => Promise; - drilldowns: () => Promise; + drilldowns: () => Promise; } export class FlyoutEditDrilldownAction implements ActionByType { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index a353eed0964bb..4bdf03dff3531 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -16,7 +16,7 @@ import { OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; -import { DrilldownsStartContract } from '../../../../drilldowns/public'; +import { DrilldownsStart } from '../../../../drilldowns/public'; import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown'; declare module '../../../../../../src/plugins/ui_actions/public' { @@ -32,7 +32,7 @@ interface BootstrapParams { export class DashboardDrilldownsService { bootstrap( - core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, + core: CoreSetup<{ drilldowns: DrilldownsStart }>, plugins: SetupDependencies, { enableDrilldowns }: BootstrapParams ) { @@ -41,10 +41,7 @@ export class DashboardDrilldownsService { } } - setupDrilldowns( - core: CoreSetup<{ drilldowns: DrilldownsStartContract }>, - plugins: SetupDependencies - ) { + setupDrilldowns(core: CoreSetup<{ drilldowns: DrilldownsStart }>, plugins: SetupDependencies) { const overlays = async () => (await core.getStartServices())[0].overlays; const drilldowns = async () => (await core.getStartServices())[1].drilldowns; const savedObjects = async () => (await core.getStartServices())[0].savedObjects.client; diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 43f17b2a64304..044e29c671de4 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -7,9 +7,9 @@ import { DrilldownsPlugin } from './plugin'; export { - SetupContract as DrilldownsSetupContract, + SetupContract as DrilldownsSetup, SetupDependencies as DrilldownsSetupDependencies, - StartContract as DrilldownsStartContract, + StartContract as DrilldownsStart, StartDependencies as DrilldownsStartDependencies, } from './plugin'; diff --git a/x-pack/plugins/drilldowns/public/mocks.ts b/x-pack/plugins/drilldowns/public/mocks.ts index bab8e83d8430e..18816243a3572 100644 --- a/x-pack/plugins/drilldowns/public/mocks.ts +++ b/x-pack/plugins/drilldowns/public/mocks.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DrilldownsSetupContract, DrilldownsStartContract } from '.'; +import { DrilldownsSetup, DrilldownsStart } from '.'; -export type Setup = jest.Mocked; -export type Start = jest.Mocked; +export type Setup = jest.Mocked; +export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { From a5d7a3852ac4b7d12f6bf685dbb1aba635e86bcb Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 14:48:39 +0100 Subject: [PATCH 68/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20do=20naming=20?= =?UTF-8?q?adjustments=20for=20dashboard=20drilldown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/action_wizard/test_data.tsx | 20 +++++++++---------- .../drilldown.tsx | 8 ++++---- .../dashboard_to_dashboard_drilldown/types.ts | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx index 4048a22ff4849..167cb130fdb4a 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/test_data.tsx @@ -20,15 +20,15 @@ export const dashboards = [ interface DashboardDrilldownConfig { dashboardId?: string; - useCurrentDashboardFilters: boolean; - useCurrentDashboardDataRange: boolean; + useCurrentFilters: boolean; + useCurrentDateRange: boolean; } function DashboardDrilldownCollectConfig(props: CollectConfigProps) { const config = props.config ?? { dashboardId: undefined, - useCurrentDashboardDataRange: true, - useCurrentDashboardFilters: true, + useCurrentFilters: true, + useCurrentDateRange: true, }; return ( <> @@ -47,11 +47,11 @@ function DashboardDrilldownCollectConfig(props: CollectConfigProps props.onConfig({ ...config, - useCurrentDashboardFilters: !config.useCurrentDashboardFilters, + useCurrentFilters: !config.useCurrentFilters, }) } /> @@ -60,11 +60,11 @@ function DashboardDrilldownCollectConfig(props: CollectConfigProps props.onConfig({ ...config, - useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, + useCurrentDateRange: !config.useCurrentDateRange, }) } /> @@ -84,8 +84,8 @@ export const dashboardDrilldownActionFactory: ActionFactoryDefinition< createConfig: () => { return { dashboardId: undefined, - useCurrentDashboardDataRange: true, - useCurrentDashboardFilters: true, + useCurrentFilters: true, + useCurrentDateRange: true, }; }, isConfigValid: (config: DashboardDrilldownConfig): config is DashboardDrilldownConfig => { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx index 8b8b012f888d5..9d2a378f08acd 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/drilldown.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { CoreStart } from 'src/core/public'; import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { FactoryContext, ActionContext, Config, CollectConfigProps } from './types'; +import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; import { CollectConfigContainer } from './collect_config'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { DrilldownDefinition as Drilldown } from '../../../../../drilldowns/public'; @@ -18,7 +18,7 @@ export interface Params { } export class DashboardToDashboardDrilldown - implements Drilldown { + implements Drilldown { constructor(protected readonly params: Params) {} public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -37,8 +37,8 @@ export class DashboardToDashboardDrilldown public readonly createConfig = () => ({ dashboardId: '123', - useCurrentDashboardDataRange: true, - useCurrentDashboardFilters: true, + useCurrentFilters: true, + useCurrentDateRange: true, }); public readonly isConfigValid = (config: Config): config is Config => { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts index 74be9c328f7f2..398a259491e3e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/types.ts @@ -10,13 +10,13 @@ import { } from '../../../../../../../src/plugins/embeddable/public'; import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; -export type FactoryContext = EmbeddableContext; +export type PlaceContext = EmbeddableContext; export type ActionContext = EmbeddableVisTriggerContext; export interface Config { dashboardId?: string; - useCurrentDashboardFilters: boolean; - useCurrentDashboardDataRange: boolean; + useCurrentFilters: boolean; + useCurrentDateRange: boolean; } export type CollectConfigProps = UiActionsCollectConfigProps; From e68dcfb77820d0fa6e5f4037d9180413c33c68a4 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 14:55:59 +0100 Subject: [PATCH 69/79] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20Type=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/lib/embeddables/embeddable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index b08eb4f6dd596..35973cc16cf9b 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -65,7 +65,7 @@ export abstract class Embeddable< // TODO: Rename to destroyed. private destoyed: boolean = false; - private storage = new EmbeddableActionStorage(this); + private storage = new EmbeddableActionStorage((this as unknown) as Embeddable); private cachedDynamicActions?: UiActionsDynamicActionManager; public get dynamicActions(): UiActionsDynamicActionManager | undefined { From 47f1095aab88a6adbaef5977f301b4af93230c6c Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 20 Mar 2020 22:09:55 +0100 Subject: [PATCH 70/79] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20TypeScript=20?= =?UTF-8?q?type=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard_to_dashboard_drilldown/collect_config.tsx | 8 ++++---- .../drilldowns/dashboard_to_dashboard_drilldown/index.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx index 778a6b3ef6b31..e463cc38b6fbf 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx @@ -33,21 +33,21 @@ export const CollectConfigContainer: React.FC = ({ { onConfig({ ...config, dashboardId }); }} onCurrentFiltersToggle={() => onConfig({ ...config, - useCurrentDashboardFilters: !config.useCurrentDashboardFilters, + useCurrentFilters: !config.useCurrentFilters, }) } onKeepRangeToggle={() => onConfig({ ...config, - useCurrentDashboardDataRange: !config.useCurrentDashboardDataRange, + useCurrentDateRange: !config.useCurrentDateRange, }) } /> diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts index 58cf5cbad346b..9daa485bb6e6c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/index.ts @@ -10,7 +10,7 @@ export { Params as DashboardToDashboardDrilldownParams, } from './drilldown'; export { - FactoryContext as DashboardToDashboardFactoryContext, + PlaceContext as DashboardToDashboardPlaceContext, ActionContext as DashboardToDashboardActionContext, Config as DashboardToDashboardConfig, } from './types'; From e678e0605b049ffcfdc0b31811a6d6c5302eebda Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 21 Mar 2020 12:38:01 +0100 Subject: [PATCH 71/79] =?UTF-8?q?test:=20=F0=9F=92=8D=20fix=20test=20after?= =?UTF-8?q?=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/actions/dynamic_action_manager.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts index 78fb4de123326..2574a9e529ebf 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts @@ -30,9 +30,10 @@ const actionFactoryDefinition1: ActionFactoryDefinition = { CollectConfig: {} as any, createConfig: () => ({}), isConfigValid: (() => true) as any, - create: () => ({ + create: ({ name }) => ({ id: '', execute: async () => {}, + getDisplayName: () => name, }), }; @@ -41,9 +42,10 @@ const actionFactoryDefinition2: ActionFactoryDefinition = { CollectConfig: {} as any, createConfig: () => ({}), isConfigValid: (() => true) as any, - create: () => ({ + create: ({ name }) => ({ id: '', execute: async () => {}, + getDisplayName: () => name, }), }; From 1604ab501b3432562f54834f46e6a12886506e0c Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 21 Mar 2020 12:54:47 +0100 Subject: [PATCH 72/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rename=20conte?= =?UTF-8?q?xt=20->=20placeContext=20in=20React=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flyout_create_drilldown.tsx | 2 +- .../flyout_edit_drilldown/flyout_edit_drilldown.tsx | 2 +- .../connected_flyout_manage_drilldowns.story.tsx | 2 +- .../connected_flyout_manage_drilldowns.test.tsx | 12 ++++++------ .../connected_flyout_manage_drilldowns.tsx | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 792238adff313..00e74ea570a11 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -61,7 +61,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} - context={context} + placeContext={context} viewMode={'create'} dynamicActionManager={dynamicActionManager} /> diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 92de8f4335add..816b757592a72 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -58,7 +58,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} - context={context} + placeContext={context} viewMode={'manage'} dynamicActionManager={dynamicActionManager} /> diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx index 5b1864a379fe4..16b4d3a25d9e5 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.story.tsx @@ -38,6 +38,6 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({ storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => ( {}}> - + )); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx index fcbbf2cd8e425..6749b41e81fc7 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.test.tsx @@ -44,7 +44,7 @@ beforeEach(() => { test('Allows to manage drilldowns', async () => { const screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async @@ -113,7 +113,7 @@ test('Allows to manage drilldowns', async () => { test('Can delete multiple drilldowns', async () => { const screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); @@ -151,7 +151,7 @@ test('Create only mode', async () => { const onClose = jest.fn(); const screen = render( { throw error; }); const screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); @@ -201,7 +201,7 @@ test("Error when can't save drilldown changes", async () => { test('Should show drilldown welcome message. Should be able to dismiss it', async () => { let screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async @@ -213,7 +213,7 @@ test('Should show drilldown welcome message. Should be able to dismiss it', asyn cleanup(); screen = render( - + ); // wait for initial render. It is async because resolving compatible action factories is async await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible()); diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 38660a72ab65c..f22ccc2f26f02 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -34,7 +34,7 @@ import { import { DrilldownFactoryContext } from '../../types'; interface ConnectedFlyoutManageDrilldownsProps { - context: Context; + placeContext: Context; dynamicActionManager: DynamicActionManager; viewMode?: 'create' | 'manage'; onClose?: () => void; @@ -76,10 +76,10 @@ export function createFlyoutManageDrilldowns({ const factoryContext: DrilldownFactoryContext = React.useMemo( () => ({ - placeContext: props.context, + placeContext: props.placeContext, triggers: selectedTriggers, }), - [props.context, selectedTriggers] + [props.placeContext, selectedTriggers] ); const actionFactories = useCompatibleActionFactoriesForCurrentContext( From 0dafa57bc9754425bcf94336f9c995531cdd0e41 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 23 Mar 2020 09:47:43 +0100 Subject: [PATCH 73/79] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20setting?= =?UTF-8?q?=20from=20kibana.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/kibana.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/kibana.yml b/config/kibana.yml index 7b11a32bbdb42..0780841ca057e 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -110,6 +110,3 @@ # Specifies locale to be used for all localizable strings, dates and number formats. # Supported languages are the following: English - en , by default , Chinese - zh-CN . #i18n.locale: "en" - -# Enables "Drilldowns" functionality on dashboard. Set to false by default. -# xpack.dashboardEnhanced.drilldowns.enabled: false From 29ee51c0c3616c0447282fcb491b3cd30faefcfa Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 23 Mar 2020 10:07:56 +0100 Subject: [PATCH 74/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20retur?= =?UTF-8?q?n=20type=20of=20getAction=20as=20per=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/service/ui_actions_service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 7c77ec0dcfc35..8bd3bb34fbbd8 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -33,6 +33,7 @@ import { ActionFactory, ActionDefinition, ActionFactoryDefinition, + ActionContext, } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; @@ -156,7 +157,9 @@ export class UiActionsService { this.attachAction(triggerId, action.id); }; - public readonly getAction = (id: string): ActionInternal => { + public readonly getAction = ( + id: string + ): Action> => { if (!this.actions.has(id)) { throw new Error(`Action [action.id = ${id}] not registered.`); } From 2b162e829e9fe2a72384dbb057958319fca1772e Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 23 Mar 2020 17:40:03 +0100 Subject: [PATCH 75/79] remove custom css per review --- .../form_drilldown_wizard/form_drilldown_wizard.scss | 4 ---- .../form_drilldown_wizard/form_drilldown_wizard.tsx | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss deleted file mode 100644 index b8507abb796b5..0000000000000 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.scss +++ /dev/null @@ -1,4 +0,0 @@ -.drdFormDrilldownWizard__formRow .euiFormRow__label { - font-size: #{$euiSize * 0.875}; // increase default euiFormRow label size - margin-bottom: $euiSizeS; // increase default euiFormRow label margin bottom -} diff --git a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx index 4c9a7a2e514c6..bdafaaf07873c 100644 --- a/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/form_drilldown_wizard/form_drilldown_wizard.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import './form_drilldown_wizard.scss'; import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { txtDrilldownAction, txtNameOfDrilldown, txtUntitledDrilldown } from './i18n'; import { @@ -40,7 +39,7 @@ export const FormDrilldownWizard: React.FC = ({ actionFactoryContext, }) => { const nameFragment = ( - + = ({ 1 ? txtDrilldownAction : undefined} fullWidth={true} - className="drdFormDrilldownWizard__formRow" > Date: Mon, 23 Mar 2020 18:21:58 +0100 Subject: [PATCH 76/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rename=20drill?= =?UTF-8?q?downCount=20to=20eventCount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/lib/panel/embeddable_panel.tsx | 16 ++++++++-------- .../lib/panel/panel_header/panel_header.tsx | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 6c58ad97f3adb..c6537f2d94994 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -65,14 +65,14 @@ interface State { hidePanelTitles: boolean; closeContextMenu: boolean; badges: Array>; - drilldownCount?: number; + eventCount?: number; } export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; private subscription?: Subscription; - private drilldownCountSubscription?: Subscription; + private eventCountSubscription?: Subscription; private mounted: boolean = false; private generateId = htmlIdGenerator(); @@ -146,8 +146,8 @@ export class EmbeddablePanel extends React.Component { if (this.subscription) { this.subscription.unsubscribe(); } - if (this.drilldownCountSubscription) { - this.drilldownCountSubscription.unsubscribe(); + if (this.eventCountSubscription) { + this.eventCountSubscription.unsubscribe(); } if (this.parentSubscription) { this.parentSubscription.unsubscribe(); @@ -190,7 +190,7 @@ export class EmbeddablePanel extends React.Component { badges={this.state.badges} embeddable={this.props.embeddable} headerId={headerId} - drilldownCount={this.state.drilldownCount} + eventCount={this.state.eventCount} /> )}
@@ -205,10 +205,10 @@ export class EmbeddablePanel extends React.Component { const dynamicActions = this.props.embeddable.dynamicActions; if (dynamicActions) { - this.setState({ drilldownCount: dynamicActions.state.get().events.length }); - this.drilldownCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { + this.setState({ eventCount: dynamicActions.state.get().events.length }); + this.eventCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { if (!this.mounted) return; - this.setState({ drilldownCount: events.length }); + this.setState({ eventCount: events.length }); }); } } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index ed1ae739502a7..2a856af7ae916 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -41,7 +41,7 @@ export interface PanelHeaderProps { badges: Array>; embeddable: IEmbeddable; headerId?: string; - drilldownCount?: number; + eventCount?: number; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -92,7 +92,7 @@ export function PanelHeader({ badges, embeddable, headerId, - drilldownCount, + eventCount, }: PanelHeaderProps) { const viewDescription = getViewDescription(embeddable); const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== ''; @@ -150,9 +150,9 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - {!isViewMode && !!drilldownCount && ( + {!isViewMode && !!eventCount && ( - {drilldownCount} + {eventCount} )} Date: Mon, 23 Mar 2020 18:23:54 +0100 Subject: [PATCH 77/79] =?UTF-8?q?style:=20=F0=9F=92=84=20remove=20any?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/actions/dynamic_action_manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts index 5f6ee86a56c3e..97eb5b05fbbc2 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts @@ -81,7 +81,7 @@ export class DynamicActionManager { const actionId = this.generateActionId(eventId); const factory = uiActions.getActionFactory(event.action.factoryId); - const actionDefinition: ActionDefinition = { + const actionDefinition: ActionDefinition = { ...factory.create(action as SerializedAction), id: actionId, isCompatible, From e29030f3176cddabc807819bdb9a01d487214ef8 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 23 Mar 2020 18:29:04 +0100 Subject: [PATCH 78/79] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20how?= =?UTF-8?q?=20uiActions=20are=20passed=20to=20vis=20embeddable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/embeddable/visualize_embeddable_factory.tsx | 6 ++++-- .../visualizations/public/np_ready/public/plugin.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx index 1ee522697fff8..1f53aac1ff073 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable_factory.tsx @@ -54,7 +54,9 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< public readonly type = VISUALIZE_EMBEDDABLE_TYPE; constructor( - private readonly getServices: () => Promise> + private readonly getUiActions: () => Promise< + Pick['uiActions'] + > ) { super({ savedObjectMetaData: { @@ -117,7 +119,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const indexPattern = await getIndexPattern(savedObject); const indexPatterns = indexPattern ? [indexPattern] : []; - const { uiActions } = await this.getServices(); + const uiActions = await this.getUiActions(); const editable = await this.isEditable(); return new VisualizeEmbeddable( diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index 484c9e0b2421a..47f2516149606 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -115,7 +115,7 @@ export class VisualizationsPlugin expressions.registerRenderer(visualizationRenderer); const embeddableFactory = new VisualizeEmbeddableFactory( - async () => (await core.getStartServices())[1] + async () => (await core.getStartServices())[1].uiActions ); embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); From 6cd07c5f9669e82fb59ff2d4c7c678a731709a89 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 23 Mar 2020 20:12:56 +0100 Subject: [PATCH 79/79] =?UTF-8?q?style:=20=F0=9F=92=84=20remove=20unused?= =?UTF-8?q?=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/np_ready/public/embeddable/visualize_embeddable.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index a3273f0f4574e..4b21be83f1722 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -45,7 +45,6 @@ import { PersistedState } from '../../../../../../../plugins/visualizations/publ import { buildPipeline } from '../legacy/build_pipeline'; import { Vis } from '../vis'; import { getExpressions, getUiActions } from '../services'; -import { VisSavedObject } from '../types'; import { VisualizationsStartDeps } from '../plugin'; import { VIS_EVENT_TO_TRIGGER } from './events';