From 502a60a4efe478cdb6e95b6e95dc6b60e579548d Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 9 Mar 2020 09:57:15 +0100 Subject: [PATCH 001/129] 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 ed224248ec4cdf..3e7d0bf79bdc39 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 3be289fe6d46e8..236b1a6ec4611e 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 c11c1119a9b130..0619c12b9b40e2 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 b951c7dc1fc875..8372d871663646 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 4834cc80813742..0ce10b2a801e34 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 00000000000000..241f56731ab7ba --- /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 7a9e19342f27ce..c4a4630397f1c2 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 1ef714f7b86e2e..bec9d0145c2b3b 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 00000000000000..63dc95dabc0fbf --- /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 3748fc666c81c5..00000000000000 --- 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 4f024b7d9cd6a2..00000000000000 --- 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 b45ac9197c7e06..00000000000000 --- 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 00000000000000..a6395db54560c1 --- /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 00000000000000..fcc5bcf2fa8b84 --- /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 00000000000000..44f4a9c041a3ba --- /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 ce235043b4ef6e..96ed23bf112c9a 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 2715637f6392fe..cb223db556f560 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 b5fb52fcf5c18a..0a3989487745f9 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 2945cfd739482d..659fc895b19a55 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 257d7d36dbee19..23af89ebf9bc7e 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 00000000000000..d3146bbc9d3edb --- /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 00000000000000..a44a7ccccb4dce --- /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 00000000000000..0dd4e37d4dddd7 --- /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 00000000000000..f8c9d224fb2923 --- /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 00000000000000..33feea11c4f29a --- /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 00000000000000..207cb3e42d2b3d --- /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 ceabc6d3a9aa51..460fcf2e06c0e7 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 00000000000000..c1c530977a1220 --- /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 e7e1d67473e8c7..00000000000000 --- 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 4422de604092bb..00000000000000 --- 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 00000000000000..b8507abb796b52 --- /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 00000000000000..dbf7d4c35769c0 --- /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 6691966e47e647..b4707eef79a68d 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 00000000000000..19ec5b7506c1b1 --- /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 4c0e287935edd7..2dfcd917a79008 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 c2c5a7e435b391..4aea824de00d7f 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 00000000000000..fbc7c9dcfb4a18 --- /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 00000000000000..82b6ce27af6d4c --- /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 5627a5d6f4522b..ae1b063449928c 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 00000000000000..2d8a3ef8440cbc --- /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 00000000000000..a9895444558cd0 --- /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 00000000000000..862c3efafa1897 --- /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 002/129] 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 8ed64f004c9be3..52f618f611ca42 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 fdff82e63faec1..505399f3150bec 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 28474544f40b57..fc460fbcc17e94 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 36903f2d7c90f7..90823359359a18 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 2b2fc004a84c62..789542766f5f50 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 00000000000000..9adba9313a36da --- /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 00000000000000..6f1528fea5f42c --- /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 00000000000000..0ff139ff7ae4f3 --- /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 00000000000000..ce571a95acddf8 --- /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 00000000000000..69f218171520c7 --- /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 64bfd368e3dfa1..573eaca74dec21 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 00000000000000..f4aaa67d43d76d --- /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 00000000000000..16629f4c092da8 --- /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 00000000000000..c574aef9c24fe9 --- /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 00000000000000..f876aba7c13eca --- /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 00000000000000..94c3f7b96b8c1f --- /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 00000000000000..5d64bbd8131649 --- /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 00000000000000..09fcfcd1d19dba --- /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 00000000000000..ec0c3c533b20a0 --- /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 00000000000000..a38fe0fb8e1902 --- /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 3dce2c1f4c257e..327f0ec15137fb 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 79b8e1474f6c20..1844388b918845 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 bdf71a25e6dbc3..c9395e15854ec6 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 f7718e63773f5e..4bba77a4e23036 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 f5a6a96fb41a40..53960c2d472661 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 00000000000000..93d8098a7e2944 --- /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 7d63b1b6d5669f..312ba2353a2a51 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 d443ce0e592cb9..51990d8158d7a3 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 00000000000000..2d89443dd65ded --- /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 00000000000000..0eb3340993f5fd --- /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 00000000000000..fbd39bc6f1f909 --- /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 00000000000000..cb2eda610170d0 --- /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 003/129] 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 e69361178eeba1..dc8d1f27c91368 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 0b5fd8184deb10..16622cc2105b9c 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 fc460fbcc17e94..d27dbc14a86d80 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 a348e1ed79d8db..6fc78e46eb23ac 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 327f0ec15137fb..98c0e32713d749 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 aa8df8b6965d8d..96df8f7601b0b6 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 1844388b918845..2ac34f6795dc49 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 0ce10b2a801e34..a54648924de226 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 f109da94fcaca7..e5990f6157bc64 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 7209045191e94a..131b9dc9b9160a 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 004/129] 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 789542766f5f50..119a152dba7285 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 0ff139ff7ae4f3..0edd5e3eaa602d 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 2ac34f6795dc49..88ad8f5d6386e6 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 2d89443dd65ded..4daa601e937790 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 fbd39bc6f1f909..464802eccd0b64 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 41ef863c00e441..930ec7e15cb017 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 3e7d0bf79bdc39..a189afbf956eec 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 8ecdde681069e8..d08bd299dddd85 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 0619c12b9b40e2..abecbf354734cf 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 2f6935cdf1961f..73592cb18cebf5 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 00000000000000..be0b69d4830373 --- /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 00000000000000..5c9879f11b2f55 --- /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 a54648924de226..f0af6d2c9545e1 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 e5990f6157bc64..d8f290ac9f4ba4 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 a6395db54560c1..f61fbfbb544450 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 fcc5bcf2fa8b84..d8d7e253bb81ac 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 33feea11c4f29a..4fbe874f015612 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 207cb3e42d2b3d..152b49f62fddd3 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 b89172541b91e3..ab7f5cc0f52f64 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 131b9dc9b9160a..4149bfada84d1a 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 005/129] 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 de74a2c42be8b6..e18e1c448e4066 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 98c0e32713d749..e4c89b6a3bb8a9 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 88ad8f5d6386e6..f52d1e2af9a88d 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 00000000000000..d9296ae158621d --- /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 00000000000000..e871bee3e46259 --- /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 00000000000000..8f48bae9f38030 --- /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 00000000000000..67dc1fd97d5213 --- /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 00000000000000..9943817631db2a --- /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 f0af6d2c9545e1..7c98348b307f32 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 d8f290ac9f4ba4..7bd2030786117c 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 00000000000000..fb9164227156a1 --- /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 00000000000000..7be8f1c65da126 --- /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 00000000000000..8cc3e129065311 --- /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 bfade1674072ac..97a430fad260a4 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 ab7f5cc0f52f64..7a2947913d43f6 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 006/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20"implement"=20re?= =?UTF-8?q?gisterDrilldown()=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 63e7a122354620..48eeb142b65a2d 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 7a2947913d43f6..b4124704a1a7c5 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 983db1ee9f945a..6a27094b804577 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 007/129] 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 f52d1e2af9a88d..21107608b5f11d 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 4daa601e937790..d2586db52ec9f1 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 008/129] 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 9943817631db2a..447ceff4b8cb2c 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 7c98348b307f32..459f7f568ea78f 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 7bd2030786117c..47a82c86d321be 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 fb9164227156a1..74be910255b176 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 00000000000000..851eac616d24e2 --- /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 00000000000000..f084a3e563c23d --- /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 2dfcd917a79008..ba6975ebeed3c6 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 97a430fad260a4..bab8e83d8430e6 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 b4124704a1a7c5..c62a539fe16886 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 009/129] 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 abecbf354734cf..04fca487214c0f 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 73592cb18cebf5..8acf89fe5a37d2 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 4fbe874f015612..a2b1975a4c428c 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 00000000000000..299f7672ac7145 --- /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 851eac616d24e2..a49dadb527d5e2 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 d8d7e253bb81ac..8c53e9a7fb8cc4 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 152b49f62fddd3..00000000000000 --- 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 460fcf2e06c0e7..00000000000000 --- 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 c1c530977a1220..00000000000000 --- 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 010/129] 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 930ec7e15cb017..7bd386a52509be 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 a9895444558cd0..cc61ac924ebedc 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 011/129] 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 a2b1975a4c428c..7031db9987b1bc 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 299f7672ac7145..540eeb056321eb 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 a49dadb527d5e2..de80928b68668f 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 bec9d0145c2b3b..60ac90a954c279 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 19ec5b7506c1b1..6913097c7c4568 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 c62a539fe16886..d2fa88bd217453 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 012/129] 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 21107608b5f11d..08d20acd63ce47 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 464802eccd0b64..daaf316cf402dc 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 7bd386a52509be..b88cee84e0e21f 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 d08bd299dddd85..e88399bdc07be5 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 04fca487214c0f..74dfd3c6012b60 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 5c9879f11b2f55..45f5382c9ec1da 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 de80928b68668f..ef3372deb6429c 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 241f56731ab7ba..e527485765df34 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 8c53e9a7fb8cc4..8f6b9d4bc97ff7 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 a44a7ccccb4dce..0709608a642f33 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 6913097c7c4568..b6675398a32181 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 cc61ac924ebedc..e3383596ea3307 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 013/129] 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 56facc37fc6661..f67a41596868f0 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 520f92840c5f9c..b9a642fafeace6 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 119a152dba7285..a7bbd9f116f39e 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 9adba9313a36da..00000000000000 --- 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 6f1528fea5f42c..b14346180c2741 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 0edd5e3eaa602d..d545757108eaa6 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 ce571a95acddf8..00000000000000 --- 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 573eaca74dec21..eb004037926c7a 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 f4aaa67d43d76d..00000000000000 --- 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 16629f4c092da8..00000000000000 --- 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 c574aef9c24fe9..00000000000000 --- 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 f876aba7c13eca..00000000000000 --- 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 5d64bbd8131649..655302bf54734b 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 08d20acd63ce47..5d4932539e23ad 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 4bba77a4e23036..f86e8a3b45f819 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 93d8098a7e2944..00000000000000 --- 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 312ba2353a2a51..dfa71cec89595e 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 0eb3340993f5fd..a6943e54f016cb 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 daaf316cf402dc..adf2035940d801 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 aea47be693b8fc..cc56714fcb2f84 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 b88cee84e0e21f..3d82d9483d1e9a 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 e88399bdc07be5..1b1e52f782aca9 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 74dfd3c6012b60..582a5917d2d4a2 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 8acf89fe5a37d2..d2df3b7aace698 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 00000000000000..5e41c6d3a7fce2 --- /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 00000000000000..ee03e599a24526 --- /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 00000000000000..61c8fcc3f75548 --- /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 00000000000000..f5ee2cfa301917 --- /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 be0b69d4830373..0f8b4c8d8f409e 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 45f5382c9ec1da..00000000000000 --- 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 d2586db52ec9f1..734ccb4147e216 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 00000000000000..f6c5a2c585aaf5 --- /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 7031db9987b1bc..33feddc538219d 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 540eeb056321eb..b0be6379651501 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 ef3372deb6429c..cd56e9871338a1 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 f61fbfbb544450..25d172f5f83ce5 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 8f6b9d4bc97ff7..de501aafe90c2c 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 dbf7d4c35769c0..2fc35eb6b5298f 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 b4707eef79a68d..4560773cc8a6dd 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 b6675398a32181..d1dccccf1edb37 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 6a27094b804577..f9667e0baab600 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 014/129] 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 c590cf8f34ee07..b3456a09879a20 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 90a9415c0b497c..462ba966f4715f 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 adf2035940d801..945fd2065ce78a 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 53628ea970fb6f..39fcfe6ddb096a 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 582a5917d2d4a2..62b8ef00f29508 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 5e41c6d3a7fce2..2e9dfa03e5a3df 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 ee03e599a24526..655e457b5412a7 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 459f7f568ea78f..94c4830fb86389 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 d2fa88bd217453..ee6f591c1325bd 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 f9667e0baab600..8f7ff48270d812 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 00000000000000..fe54bcf8a34d86 --- /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 015/129] 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 7525345ccfe1bd..e21160beab8dbb 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 9e8eac08c33eac..53e724d72549c3 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 b8db611f30815f..a256faa7aab394 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 a1b332bb656174..5fa8ac19011afa 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 00000000000000..2502fe52f7da67 --- /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 00000000000000..d26d754d56d50c --- /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 b14346180c2741..2a2b639434ac29 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 d545757108eaa6..8cf120a2899c6c 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 00000000000000..563de3db13b50f --- /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 eb004037926c7a..f2c105bca9d453 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 5d4932539e23ad..e27cc04a7d7590 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 c1be6b26265254..786ee4b5bd025b 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 0874803db7d373..8d0ff41b6acde3 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 c9395e15854ec6..65a18f0460f228 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 f86e8a3b45f819..9dc463123e6afb 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 51990d8158d7a3..c2e5a1668f7f44 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 734ccb4147e216..d2586db52ec9f1 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 a6943e54f016cb..53c6109cac4ca0 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 62b8ef00f29508..cbe7b0c26db3b5 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 d2df3b7aace698..3d6aee460be1e5 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 2e9dfa03e5a3df..955b5c0d70b52f 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 655e457b5412a7..f29abe6e41b22f 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 61c8fcc3f75548..00000000000000 --- 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 f5ee2cfa301917..db5bb3aa62a163 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 f6c5a2c585aaf5..fd3ab89973348b 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 33feddc538219d..ae9dbfb359a7b2 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 b0be6379651501..b19a69104ee730 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 cd56e9871338a1..f95c176c521555 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 fe54bcf8a34d86..7904a1969d2dcd 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 016/129] 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 e21160beab8dbb..7e5ed61597877c 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 62121cb0f23dd5..7b1e3076142bd6 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 2502fe52f7da67..3f00121a9c5776 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 d26d754d56d50c..eaeca9fd4383e5 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 8cf120a2899c6c..ef27b78464f2b4 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 462ba966f4715f..5b10a8dc83a62e 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 563de3db13b50f..d48d78e896c9a1 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 69f218171520c7..a92909261da32e 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 f2c105bca9d453..0ddba197aced6c 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 b3456a09879a20..465f091e45ef1d 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 e27cc04a7d7590..bb0a4c95f7e8f2 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 65a18f0460f228..2d946a8d706abc 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 9dc463123e6afb..e7724c3ac92ffc 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 1b1e52f782aca9..56b9dabaec001f 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 8f7ff48270d812..185ae218a6ffa6 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 017/129] 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 499d9faa9b1d72..0bb78ff2fdc31b 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 7b1e3076142bd6..58fc7327232b8a 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 5b10a8dc83a62e..8f1cd23715d3f1 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 d48d78e896c9a1..489de32d120329 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 786ee4b5bd025b..34b256e4a9efb0 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 e7724c3ac92ffc..7f6c2a2d415725 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 00000000000000..4e2e5eb7092e45 --- /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 2fefcb50b7afc1..251ee2bf906ee9 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 00000000000000..fd46dafd36b733 --- /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 74be910255b176..6b0844aa9e20da 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 018/129] 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 489de32d120329..fbf1092de29af4 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 bb0a4c95f7e8f2..cf3c377db148cd 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 94c4830fb86389..2fe010fdf9d9e6 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 251ee2bf906ee9..9ff3ed7105866d 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 ae9dbfb359a7b2..deb47fa0930007 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 b19a69104ee730..b11de5c6810836 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 f95c176c521555..110f70632ecc00 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 00000000000000..bca343f51d4492 --- /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 de501aafe90c2c..71cea97fcad694 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 019/129] =?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 f67a41596868f0..eada20721d3e07 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 b9a642fafeace6..dc8466607a9849 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 020/129] 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 58fc7327232b8a..abb98f55a1e485 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 d27dbc14a86d80..98ea706182ddf2 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 99516a1d21d6fb..6a6a3510602d1e 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 021/129] 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 abb98f55a1e485..58fc7327232b8a 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 98ea706182ddf2..980b17ff17f1c3 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 022/129] 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 cf3c377db148cd..fb2ba1f9f340d3 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 3d82d9483d1e9a..06ab649ae08223 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 3d6aee460be1e5..c0effa483cbfb1 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 2fe010fdf9d9e6..a6be110c44a022 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 9ff3ed7105866d..6e94c8ac202adc 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 deb47fa0930007..dbb9a4d63ef6ee 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 b11de5c6810836..cb52d5611810a6 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 110f70632ecc00..b55c17eb527a08 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 bca343f51d4492..a4d09dddbaf310 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 60ac90a954c279..6c975fe3247447 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 71cea97fcad694..b7ed60084473ca 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 d3146bbc9d3edb..0529f0451b16a0 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 0709608a642f33..a44a7ccccb4dce 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 ae1b063449928c..f700b2bdc36975 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 2d8a3ef8440cbc..4a4d67b08b1d37 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 e3383596ea3307..bc32a0d4c5dfad 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 862c3efafa1897..00000000000000 --- 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 185ae218a6ffa6..14207611a0440c 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 023/129] 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 52f618f611ca42..370abc120d475d 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 e871bee3e46259..a9b6920ae369ef 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 00000000000000..8081f8a2451cfa --- /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 00000000000000..8e204b044a1368 --- /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 00000000000000..911ff6f6326357 --- /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 00000000000000..b45ba602b9bb1b --- /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 00000000000000..38fe6dd1508536 --- /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 00000000000000..b9a64a3cc17e6e --- /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 00000000000000..b9a64a3cc17e6e --- /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 6b0844aa9e20da..ed0cb425ee106f 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 00000000000000..95101605ce4682 --- /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 00000000000000..778a6b3ef6b314 --- /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 00000000000000..e2a530b156da5c --- /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 00000000000000..0fb60bb1064a1b --- /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 00000000000000..c839ef8ee04efe --- /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 00000000000000..98b746bafd24af --- /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 00000000000000..58cf5cbad346b3 --- /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 00000000000000..92f5a9be648bc0 --- /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 00000000000000..f2cbe4135f4cb6 --- /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 48eeb142b65a2d..6da0b96ad631cb 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 14207611a0440c..e258319a16b702 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 7904a1969d2dcd..4c5cfa2e596aa1 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 024/129] 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 dbb9a4d63ef6ee..5b1864a379fe49 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 cb52d5611810a6..7d6b7559ec9367 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 b55c17eb527a08..2f6f921cab18ae 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 00000000000000..70f4d735e2a749 --- /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 44f4a9c041a3ba..a4a2754a444ab3 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 ee6f591c1325bd..bbc06847d58424 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 025/129] 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 06ab649ae08223..481c3a3ef01946 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 026/129] 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 bc32a0d4c5dfad..c27ff7bd5b6bf4 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 027/129] 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 481c3a3ef01946..5d605ca1c3b7f0 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 2f6f921cab18ae..aad31191ca7357 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 028/129] 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 2ba6f9baca90d6..db09ff4a57ef92 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 641f25176264a8..a315184bf68efa 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 aad31191ca7357..866fde9f3a1d03 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 659fc895b19a55..fc803af16a266d 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 f700b2bdc36975..eafe50bab20167 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 c27ff7bd5b6bf4..5a15781a1faf2a 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 029/129] 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 980b17ff17f1c3..91846e45525fc3 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 6a6a3510602d1e..ed1ae739502a72 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 26a29bc470e8ac..29ffa4cd486b5c 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 00000000000000..f15c7cfb69743e --- /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 fbf1092de29af4..dd3d39b53de66a 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 00000000000000..2d767ebe18968e --- /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 d2586db52ec9f1..ea13bab4d2e5ee 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 c839ef8ee04efe..211a16451b30cd 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 030/129] 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 505399f3150bec..8abd2d9cab5a61 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 2a2b639434ac29..b14346180c2741 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 ef27b78464f2b4..b6652f7e8a50a0 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 2d767ebe18968e..ba42fc4d15ce7b 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 ea13bab4d2e5ee..323bacd033bd5d 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 866fde9f3a1d03..dbed6da46ba465 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 a4d09dddbaf310..aaf9a0e3dd0204 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 031/129] =?UTF-8?q?test:=20=F0=9F=92=8D=20stub=20DynamicAc?= =?UTF-8?q?tionManager=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 00000000000000..aebd451ae11b03 --- /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 032/129] 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 a7bbd9f116f39e..15f1d6dd79289f 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 3f00121a9c5776..ae20e8b924b19e 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 eaeca9fd4383e5..8cc9215061e190 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 b6652f7e8a50a0..245ded991c0324 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 dd3d39b53de66a..ae6c97fcc78df7 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 94c3f7b96b8c1f..969e3dbac0e679 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 09fcfcd1d19dba..93ef139cf19408 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 7f6c2a2d415725..824be69a991a85 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 c2e5a1668f7f44..9fab2a77857933 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 323bacd033bd5d..7d2ceb61c106ea 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 5d605ca1c3b7f0..f6fcbba7f4a9ad 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 56b9dabaec001f..3787c1651a5864 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 cbe7b0c26db3b5..55361105dcb0f0 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 955b5c0d70b52f..66e2a4eafa880b 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 f29abe6e41b22f..f8669a4bf813f3 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 dbed6da46ba465..9462b89fc26ffc 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 25d172f5f83ce5..f332bfc3cecba2 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 b7ed60084473ca..faa965a98a4bb4 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 d1dccccf1edb37..4d9f0f8e13a778 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 033/129] =?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 fb2ba1f9f340d3..e0ac61a55081b0 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 034/129] 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 e0ac61a55081b0..f674179a217aa8 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 824be69a991a85..deacf61af41a83 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 f6fcbba7f4a9ad..783b0e1d4aff79 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 a6be110c44a022..9ef9800ab90e6e 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 ed0cb425ee106f..6695811500e732 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 211a16451b30cd..e80f4a24a56deb 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 92f5a9be648bc0..74be9c328f7f2c 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 e258319a16b702..3f89a9f5d64418 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 4c5cfa2e596aa1..21e28d8a1e64f3 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 035/129] 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 fc803af16a266d..199bf57cd59d29 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 036/129] 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 783b0e1d4aff79..1716a7afa8741f 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 037/129] =?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 b609b2ce1d7415..444430175d4f23 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 9ef9800ab90e6e..c4c774e2caf96c 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 6e94c8ac202adc..b658d44e8799ac 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 199bf57cd59d29..fc803af16a266d 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 038/129] 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 26d9c5c8ad4dd9..2a599071333402 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 205fd5be8d9ff2..c8c4f0b95c4587 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 90ab0c0d735b31..1474f9ed630525 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 82f8e33b7ae2f1..ebf5dedb3f5131 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 0bb78ff2fdc31b..12b980638a6ca7 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 91846e45525fc3..8039ec9534d22d 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 c0e43c0538833b..36957c3b794918 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 d04f35715537cb..ae9645767b267c 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 ee7948f3d6a4ae..a6d4128f3f1067 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 37a7bb12a29cc8..0052403816eb84 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 e4c89b6a3bb8a9..05c8c1c0f30890 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 96df8f7601b0b6..aa8df8b6965d8d 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 f674179a217aa8..789604fcbcf1f6 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 325a5ddc101793..c0cd8d5540db2b 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 b658d44e8799ac..b8d20731eecd3e 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 6695811500e732..a8554d9e2339f4 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 fb0eb6e4bf8046..845cad8c83feea 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 0a9c82afaec1d4..61d856d2a7a00e 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 039/129] 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 12b980638a6ca7..d272e8a3d922d2 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 dc8466607a9849..b224a67144df4e 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 58fc7327232b8a..69b7792be9eb64 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 ae6c97fcc78df7..a40abb271d85a6 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 a92909261da32e..820cc59f770284 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 789604fcbcf1f6..9265d35bad9a92 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 7d6b7559ec9367..c115c18a4f3e83 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 9462b89fc26ffc..ee1afbeb2ed0c1 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 aaf9a0e3dd0204..a024c4ad387489 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 040/129] 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 ae20e8b924b19e..bc0ec844d00f5f 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 7d2ceb61c106ea..fe235d4b5daacc 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 1716a7afa8741f..466f22d7856858 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 e180753b519366..3b4f536006cdec 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 c115c18a4f3e83..60825f893903ec 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 ee1afbeb2ed0c1..a0f18589bc2383 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 21e28d8a1e64f3..f34aa89ddd211d 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 041/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20stop=20infinite?= =?UTF-8?q?=20re-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 17541b4e5f9b86..83d3d5e10761bd 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 a0f18589bc2383..137be465fcd87c 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 042/129] 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 7f2ad2de1c4a81..1567f904e951a9 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 820cc59f770284..b3c5749680319a 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 34b256e4a9efb0..d1da0b81a7d0e0 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 deacf61af41a83..da91e4fd909f6f 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 60825f893903ec..fcbbf2cd8e425f 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 137be465fcd87c..2a7e2f28cb9918 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 a024c4ad387489..b8deaa8b842bc9 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 043/129] "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 1531c1d22b01bc..5b6897e58a8e9a 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 db09ff4a57ef92..87ec3f8fc7ec10 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 466f22d7856858..846f6d41eb30d1 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 e527485765df34..00000000000000 --- 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 6c975fe3247447..fb3a50eb77f2c0 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 f332bfc3cecba2..152cd393b9d3ec 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 4d9f0f8e13a778..4c9a7a2e514c68 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 ba6975ebeed3c6..e9b19ab0afa973 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 044/129] 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 00000000000000..190dc705e3811a --- /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 00000000000000..4d2db209fc9619 --- /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 045/129] 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 eada20721d3e07..83fd3f184e0987 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 fd46dafd36b733..94f1485a8cbc39 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 fb3a50eb77f2c0..8c6739a8ad6c8a 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 fc803af16a266d..b55cbd88d0dc06 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 046/129] 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 190dc705e3811a..31ee9e29938cb5 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 00000000000000..a3f11eb976f907 --- /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 00000000000000..91be24ab0087c6 --- /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 b8d20731eecd3e..3e1b37f270708d 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 00000000000000..be693fadf9282c --- /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 fd46dafd36b733..4f99fca511b073 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 00000000000000..9b156b0ba85b44 --- /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 047/129] 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 c47746d4b3fd6e..d053f7e82862c2 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 462f5c3bf88ba9..f08b8bb29bdd35 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 f1895905a45e16..de86b51aee3a8c 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 8a6e747aac1705..d663c736e5aed6 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 a81d80b440e049..4aede3f3442fb8 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 a01c1337122068..1dbaed6aac6bd0 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 9655922ef07b82..6e2d0ebe3aae92 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 ba42fc4d15ce7b..636af076ea39f1 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 b3c5749680319a..28550a671782ef 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 d1da0b81a7d0e0..4de38eb5421e98 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 f886781c2ee301..88a5cb04eac6f8 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 2d946a8d706abc..18262f0945cf85 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 da91e4fd909f6f..7c77ec0dcfc354 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 5b427f918c1732..ade21ee4b7d913 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 53960c2d472661..55ccac42ff255a 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 c5e68e5d5ca5a6..21dd17ed82e3fc 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 18ceec652392d1..8ddb2e1a4803b1 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 8395fddece2a49..7c7cc689d05e58 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 4b09be4db8a60d..e034fbe3206088 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 133733657b8aff..71ea72499d87fd 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 a8554d9e2339f4..82e88bb6efeb56 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 08ba10ff692078..ac46d84469513a 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 048/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20don't=20show=20"?= =?UTF-8?q?OPTIONS"=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 05c8c1c0f30890..ec58261d9e4f7e 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 c638db0ce9dab8..9758508dc3dacc 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 5b670df354f787..9885ed3abe93b1 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 ad32bdc1b564e1..2671584d105c87 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 049/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20server-sid?= =?UTF-8?q?e=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 a9b6920ae369ef..8d51714ae440cb 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 00000000000000..b75c95d5f8832e --- /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 00000000000000..05bdc3b3113e55 --- /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 050/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20disable=20Drilld?= =?UTF-8?q?owns=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 1567f904e951a9..7f2ad2de1c4a81 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 051/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20enable=20drilldo?= =?UTF-8?q?wns=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 8d51714ae440cb..95d6eb044ac3be 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 8f48bae9f38030..53540a4a1ad2e9 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 447ceff4b8cb2c..853b3e7b2ffcd9 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 82e88bb6efeb56..a353eed0964bbb 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 05bdc3b3113e55..e361b9fb075eda 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 052/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20feature=20?= =?UTF-8?q?flag=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 0780841ca057ed..7b11a32bbdb42f 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 053/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20remove=20places?= =?UTF-8?q?=20from=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 e80f4a24a56deb..c5c61c21257fcd 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 3f89a9f5d64418..8fd4b622cc2b3a 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 f34aa89ddd211d..7d34151b330845 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 054/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20pla?= =?UTF-8?q?ce=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 2a7e2f28cb9918..e3f793bea81a1c 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 7d34151b330845..c04145767338a7 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 055/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20doExec?= =?UTF-8?q?ute?= 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 c04145767338a7..8e4f8e2cbfbab2 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 056/129] 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 969e3dbac0e679..00000000000000 --- 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 655302bf54734b..00000000000000 --- 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 93ef139cf19408..00000000000000 --- 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 ec0c3c533b20a0..00000000000000 --- 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 a38fe0fb8e1902..00000000000000 --- 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 057/129] 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 5b6897e58a8e9a..1531c1d22b01bc 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 058/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20Drilld?= =?UTF-8?q?ownDefinition=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 c5c61c21257fcd..8b8b012f888d59 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 e3f793bea81a1c..38660a72ab65cc 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 6da0b96ad631cb..43f17b2a643043 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 8fd4b622cc2b3a..af53656858b334 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 8e4f8e2cbfbab2..a8232887f9ca6b 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 059/129] =?UTF-8?q?style:=20=F0=9F=92=84=20replace=20any?= =?UTF-8?q?=20by=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 90a6e16faeed5d..b08eb4f6dd5962 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 6e2d0ebe3aae92..7ca1e62310e589 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 060/129] =?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 3787c1651a5864..4048a22ff48495 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 061/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20make=20isConfig?= =?UTF-8?q?Valid=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 fe235d4b5daacc..d3a527a2183b11 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 062/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20getDi?= =?UTF-8?q?splayName=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 7ca1e62310e589..5f6ee86a56c3e3 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 18262f0945cf85..41e2b57d53dd85 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 af53656858b334..bfbe514d460955 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 063/129] =?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 b224a67144df4e..fad5b4d535d6cd 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 064/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20Action?= =?UTF-8?q?FactoryDefinition?= 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 8cc9215061e190..7ac94a41e70767 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 065/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20vis?= =?UTF-8?q?ualize=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 b2abe67dcc608d..1ee522697fff8a 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 a553eea5b6f0b0..484c9e0b2421ad 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 066/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20dashboard?= =?UTF-8?q?=20dependency=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 95d6eb044ac3be..acbca5c33295c0 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 067/129] =?UTF-8?q?style:=20=F0=9F=92=84=20rename=20drilld?= =?UTF-8?q?own=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 853b3e7b2ffcd9..30b3f3c080f493 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 3b4f536006cdec..792238adff313d 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 91be24ab0087c6..92de8f4335addc 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 a353eed0964bbb..4bdf03dff3531a 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 43f17b2a643043..044e29c671de40 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 bab8e83d8430e6..18816243a3572d 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 068/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20do=20naming?= =?UTF-8?q?=20adjustments=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 4048a22ff48495..167cb130fdb4aa 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 8b8b012f888d59..9d2a378f08acd7 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 74be9c328f7f2c..398a259491e3ef 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 069/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20Type=20erro?= =?UTF-8?q?r?= 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 b08eb4f6dd5962..35973cc16cf9bf 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 070/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20TypeScript?= =?UTF-8?q?=20type=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 778a6b3ef6b314..e463cc38b6fbf7 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 58cf5cbad346b3..9daa485bb6e6c5 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 071/129] =?UTF-8?q?test:=20=F0=9F=92=8D=20fix=20test=20aft?= =?UTF-8?q?er=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 78fb4de123326c..2574a9e529ebfd 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 072/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rename=20con?= =?UTF-8?q?text=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 792238adff313d..00e74ea570a11a 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 92de8f4335addc..816b757592a721 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 5b1864a379fe49..16b4d3a25d9e5d 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 fcbbf2cd8e425f..6749b41e81fc70 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 38660a72ab65cc..f22ccc2f26f02f 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 073/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20settin?= =?UTF-8?q?g=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 7b11a32bbdb42f..0780841ca057ed 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 074/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20ret?= =?UTF-8?q?urn=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 7c77ec0dcfc354..8bd3bb34fbbd81 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 075/129] 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 b8507abb796b52..00000000000000 --- 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 4c9a7a2e514c68..bdafaaf07873c5 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 076/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rename=20dri?= =?UTF-8?q?lldownCount=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 6c58ad97f3adb6..c6537f2d949941 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 ed1ae739502a72..2a856af7ae9161 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 077/129] =?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 5f6ee86a56c3e3..97eb5b05fbbc21 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 078/129] =?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 1ee522697fff8a..1f53aac1ff073f 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 484c9e0b2421ad..47f25161496061 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 079/129] =?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 a3273f0f4574e9..4b21be83f17224 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'; From f6295703436e5ec2cc90928f9dada9bd5bf3bdd6 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 3 Apr 2020 14:46:47 +0200 Subject: [PATCH 080/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20pass=20in=20uiAct?= =?UTF-8?q?ions=20to=20visualize=5Fembeddable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/embeddable/create_vis_embeddable_from_object.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts index bf2d174f594b2f..753f7979bb14cb 100644 --- a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts +++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts @@ -27,6 +27,7 @@ import { getHttp, getTimeFilter, getCapabilities, + getUiActions, } from '../services'; export const createVisEmbeddableFromObject = async ( @@ -58,6 +59,7 @@ export const createVisEmbeddableFromObject = async ( indexPatterns, editUrl, editable, + uiActions: getUiActions(), }, input, parent From b961a19e94e990045397afe7adc82dc2b0e739a9 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 3 Apr 2020 16:52:44 +0200 Subject: [PATCH 081/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correctly=20regis?= =?UTF-8?q?ter=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/kbn_sample_panel_action/public/plugin.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/public/plugin.ts b/test/plugin_functional/plugins/kbn_sample_panel_action/public/plugin.ts index 8ea8d2ff49e3be..9ae1021227315f 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/public/plugin.ts +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/public/plugin.ts @@ -27,14 +27,10 @@ export class SampelPanelActionTestPlugin implements Plugin { public setup(core: CoreSetup, { uiActions }: { uiActions: UiActionsSetup }) { const samplePanelAction = createSamplePanelAction(core.getStartServices); - - uiActions.registerAction(samplePanelAction); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, samplePanelAction); - const samplePanelLink = createSamplePanelLink(); - uiActions.registerAction(samplePanelLink); - uiActions.attachAction(CONTEXT_MENU_TRIGGER, samplePanelLink); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, samplePanelAction); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, samplePanelLink); return {}; } From 1b9055b6ea41ee7953b87e57c28f9912939592a6 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 9 Apr 2020 11:07:46 +0200 Subject: [PATCH 082/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20type=20erro?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/legacy/plugins/canvas/public/application.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 79b3918fef99be..670583e6542eea 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -117,7 +117,7 @@ export const initializeCanvas = async ( restoreAction = action; startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, action.id); - startPlugins.uiActions.attachAction(VALUE_CLICK_TRIGGER, emptyAction); + startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, emptyAction); } return canvasStore; @@ -129,7 +129,7 @@ export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDe startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, emptyAction.id); if (restoreAction) { - startPlugins.uiActions.attachAction(VALUE_CLICK_TRIGGER, restoreAction); + startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, restoreAction); restoreAction = undefined; } From c2ea2e37dfac5a4d1df3c76df592422aebffbd32 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 9 Apr 2020 13:30:05 +0200 Subject: [PATCH 083/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20unused?= =?UTF-8?q?=20translations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/translations/translations/ja-JP.json | 4 ---- x-pack/plugins/translations/translations/zh-CN.json | 4 ---- 2 files changed, 8 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 626199c599ec89..103eed24a68525 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6342,13 +6342,9 @@ "xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "1つ以上の引数", "xpack.data.query.queryBar.cancelLongQuery": "キャンセル", "xpack.data.query.queryBar.runBeyond": "タイムアウトを越えて実行", - "xpack.drilldowns.components.FlyoutCreateDrilldown.CreateDrilldown": "ドリルダウンを作成", - "xpack.drilldowns.components.FlyoutFrame.Close": "閉じる", "xpack.drilldowns.components.FormCreateDrilldown.drilldownAction": "ドリルダウンアクション", "xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown": "ドリルダウンの名前", "xpack.drilldowns.components.FormCreateDrilldown.untitledDrilldown": "無題のドリルダウン", - "xpack.drilldowns.FlyoutCreateDrilldownAction.displayName": "ドリルダウンを作成", - "xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName": "ドリルダウンを管理", "xpack.endpoint.alertList.viewTitle": "アラートは有効な Rison エンコード文字列でなければなりません", "xpack.endpoint.alerts.errors.bad_rison": "", "xpack.endpoint.alerts.errors.before_cannot_be_used_with_after": "[before] を [after] と併用することはできません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index de7e520f0fd0d1..acd4d2941520ee 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6344,13 +6344,9 @@ "xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "一个或多个参数", "xpack.data.query.queryBar.cancelLongQuery": "取消", "xpack.data.query.queryBar.runBeyond": "运行超时", - "xpack.drilldowns.components.FlyoutCreateDrilldown.CreateDrilldown": "创建向下钻取", - "xpack.drilldowns.components.FlyoutFrame.Close": "关闭", "xpack.drilldowns.components.FormCreateDrilldown.drilldownAction": "向下钻取操作", "xpack.drilldowns.components.FormCreateDrilldown.nameOfDrilldown": "向下钻取的名称", "xpack.drilldowns.components.FormCreateDrilldown.untitledDrilldown": "未命名向下钻取", - "xpack.drilldowns.FlyoutCreateDrilldownAction.displayName": "创建向下钻取", - "xpack.drilldowns.panel.openFlyoutEditDrilldown.displayName": "管理向下钻取", "xpack.endpoint.alertList.viewTitle": "告警", "xpack.endpoint.alerts.errors.bad_rison": "必须是有效的 rison 编码字符串", "xpack.endpoint.alerts.errors.before_cannot_be_used_with_after": "[before] 不能与 [after] 一起使用", From 57ddccfd666a72585370d7d8968bcb1f3041ca51 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Thu, 9 Apr 2020 15:31:24 +0200 Subject: [PATCH 084/129] Dynamic actions to xpack (#62647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 set up sample action factory provider * feat: 🎸 create dashboard_enhanced plugin * feat: 🎸 add EnhancedEmbeddable interface * refactor: 💡 move DynamicActionManager to x-pack * feat: 🎸 connect dynamic action manager to embeddable life-cycle * test: 💍 fix Jest tests after refactor * fix: 🐛 fix type error Co-authored-by: Elastic Machine --- src/plugins/embeddable/public/index.ts | 9 +- .../public/lib/embeddables/embeddable.tsx | 51 +------ .../public/lib/embeddables/i_embeddable.ts | 5 +- .../public/lib/panel/embeddable_panel.tsx | 12 +- .../ui_actions/public/actions/index.ts | 2 - .../ui_actions/public/actions/types.ts | 6 + src/plugins/ui_actions/public/index.ts | 6 +- .../dynamic_action_manager.test.ts | 33 ++--- .../dynamic_action_manager.ts | 31 ++-- .../dynamic_action_manager_state.ts | 19 +-- .../dynamic_action_storage.ts | 21 +-- .../public/dynamic_actions/index.ts | 9 ++ .../advanced_ui_actions/public/index.ts | 8 ++ .../dashboard_enhanced/public/plugin.ts | 9 +- .../flyout_create_drilldown.test.tsx | 100 +++++++------ .../flyout_create_drilldown.tsx | 13 +- .../flyout_edit_drilldown.test.tsx | 113 ++++++++++----- .../flyout_edit_drilldown.tsx | 16 ++- .../flyout_edit_drilldown/menu_item.test.tsx | 10 +- .../flyout_edit_drilldown/menu_item.tsx | 11 +- .../drilldowns/actions/test_helpers.ts | 22 ++- .../dashboard_drilldowns_services.ts | 10 +- .../connected_flyout_manage_drilldowns.tsx | 2 +- .../test_data.ts | 6 +- x-pack/plugins/embeddable_enhanced/README.md | 1 + .../plugins/embeddable_enhanced/kibana.json | 7 + .../embeddable_action_storage.test.ts | 31 ++-- .../embeddables/embeddable_action_storage.ts | 49 +++---- .../public/embeddables/index.ts | 8 ++ .../embeddables/is_enhanced_embeddable.ts | 14 ++ .../embeddable_enhanced/public/index.ts | 22 +++ .../embeddable_enhanced/public/mocks.ts | 27 ++++ .../embeddable_enhanced/public/plugin.ts | 135 ++++++++++++++++++ .../embeddable_enhanced/public/types.ts | 21 +++ 34 files changed, 524 insertions(+), 315 deletions(-) rename {src/plugins/ui_actions/public/actions => x-pack/plugins/advanced_ui_actions/public/dynamic_actions}/dynamic_action_manager.test.ts (94%) rename {src/plugins/ui_actions/public/actions => x-pack/plugins/advanced_ui_actions/public/dynamic_actions}/dynamic_action_manager.ts (88%) rename {src/plugins/ui_actions/public/actions => x-pack/plugins/advanced_ui_actions/public/dynamic_actions}/dynamic_action_manager_state.ts (75%) rename {src/plugins/ui_actions/public/actions => x-pack/plugins/advanced_ui_actions/public/dynamic_actions}/dynamic_action_storage.ts (77%) create mode 100644 x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts create mode 100644 x-pack/plugins/embeddable_enhanced/README.md create mode 100644 x-pack/plugins/embeddable_enhanced/kibana.json rename {src/plugins/embeddable/public/lib => x-pack/plugins/embeddable_enhanced/public}/embeddables/embeddable_action_storage.test.ts (93%) rename {src/plugins/embeddable/public/lib => x-pack/plugins/embeddable_enhanced/public}/embeddables/embeddable_action_storage.ts (54%) create mode 100644 x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/index.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/mocks.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/plugin.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/types.ts diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 23275fbe8e8f02..3f671b520fb2ef 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -23,23 +23,24 @@ import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; export { - Adapters, ACTION_ADD_PANEL, - AddPanelAction, ACTION_APPLY_FILTER, + ACTION_EDIT_PANEL, + Adapters, + AddPanelAction, Container, ContainerInput, ContainerOutput, CONTEXT_MENU_TRIGGER, contextMenuTrigger, - ACTION_EDIT_PANEL, + defaultEmbeddableFactoryProvider, EditPanelAction, Embeddable, EmbeddableChildPanel, EmbeddableChildPanelProps, EmbeddableContext, - EmbeddableFactoryDefinition, EmbeddableFactory, + EmbeddableFactoryDefinition, EmbeddableFactoryNotFoundError, EmbeddableFactoryRenderer, EmbeddableInput, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index e3a6a3359132c7..3c5374d6c124c6 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -22,12 +22,7 @@ import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { TriggerContextMapping } from '../ui_actions'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { - UiActionsDynamicActionManager, - UiActionsStart, -} from '../../../../../plugins/ui_actions/public'; -import { EmbeddableContext } from '../triggers'; +import { UiActionsStart } from '../../../../../plugins/ui_actions/public'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; @@ -60,28 +55,9 @@ 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 storage = new EmbeddableActionStorage((this as unknown) as Embeddable); - - private cachedDynamicActions?: UiActionsDynamicActionManager; - public get dynamicActions(): UiActionsDynamicActionManager | undefined { - if (!this.params.uiActions) return undefined; - if (!this.cachedDynamicActions) { - this.cachedDynamicActions = new UiActionsDynamicActionManager({ - isCompatible: async (context: unknown) => - (context as EmbeddableContext).embeddable.runtimeId === this.runtimeId, - storage: this.storage, - uiActions: this.params.uiActions, - }); - } - - return this.cachedDynamicActions; - } - constructor( input: TEmbeddableInput, output: TEmbeddableOutput, @@ -111,18 +87,6 @@ export abstract class Embeddable< this.onResetInput(newInput); }); } - - if (this.dynamicActions) { - this.dynamicActions.start().catch(error => { - /* eslint-disable */ - console.log('Failed to start embeddable dynamic actions', this); - console.error(error); - /* eslint-enable */ - }); - this.storageSubscription = this.input$.subscribe(() => { - this.storage.reload$.next(); - }); - } } public getIsContainer(): this is IContainer { @@ -202,19 +166,6 @@ 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.storageSubscription) { - this.storageSubscription.unsubscribe(); - } - this.input$.complete(); this.output$.complete(); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 9a4452aceba008..e5d8e6e1b7de9d 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -18,7 +18,6 @@ */ 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'; @@ -92,9 +91,9 @@ export interface IEmbeddable< readonly runtimeId?: number; /** - * Default implementation of dynamic action API for embeddables. + * Extra abilities added to Embeddable by `*_enhanced` plugins. */ - dynamicActions?: UiActionsDynamicActionManager; + enhancements?: object; /** * A functional representation of the isContainer variable, but helpful for typescript to diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index c6537f2d949941..d4d23874cfb19d 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -203,13 +203,15 @@ export class EmbeddablePanel extends React.Component { this.props.embeddable.render(this.embeddableRoot.current); } - const dynamicActions = this.props.embeddable.dynamicActions; + const dynamicActions = (this.props.embeddable.enhancements as any)?.dynamicActions; if (dynamicActions) { this.setState({ eventCount: dynamicActions.state.get().events.length }); - this.eventCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { - if (!this.mounted) return; - this.setState({ eventCount: events.length }); - }); + this.eventCountSubscription = dynamicActions.state.state$.subscribe( + ({ events }: { events: unknown[] }) => { + if (!this.mounted) return; + this.setState({ eventCount: events.length }); + } + ); } } diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 0ddba197aced6c..0b0e60b3cf75ce 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -23,6 +23,4 @@ export * from './action_factory_definition'; 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/types.ts b/src/plugins/ui_actions/public/actions/types.ts index 465f091e45ef1d..d29e97eea532f5 100644 --- a/src/plugins/ui_actions/public/actions/types.ts +++ b/src/plugins/ui_actions/public/actions/types.ts @@ -22,3 +22,9 @@ export interface SerializedAction { readonly name: string; readonly config: Config; } + +export interface SerializedEvent { + eventId: string; + triggers: string[]; + action: SerializedAction; +} diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 9265d35bad9a92..4f00ac4a26fc39 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -31,11 +31,7 @@ export { ActionDefinition as UiActionsActionDefinition, ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, - ActionStorage as UiActionsActionStorage, - AbstractActionStorage as UiActionsAbstractActionStorage, createAction, - DynamicActionManager, - DynamicActionManagerState, IncompatibleActionError, SerializedAction as UiActionsSerializedAction, SerializedEvent as UiActionsSerializedEvent, @@ -57,4 +53,4 @@ export { applyFilterTrigger, } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; -export { ActionByType, DynamicActionManager as UiActionsDynamicActionManager } from './actions'; +export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts similarity index 94% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts index 2574a9e529ebfd..e4bd73558b3578 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts @@ -1,29 +1,18 @@ /* - * 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 { DynamicActionManager } from './dynamic_action_manager'; import { ActionStorage, MemoryActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { ActionFactoryDefinition } from './action_factory_definition'; -import { ActionRegistry } from '../types'; -import { SerializedAction } from './types'; -import { of } from '../../../kibana_utils'; +import { + UiActionsService, + UiActionsActionFactoryDefinition as ActionFactoryDefinition, + UiActionsSerializedAction as SerializedAction, + UiActionsActionInternal as ActionInternal, +} from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils'; const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -82,7 +71,7 @@ const event3: SerializedEvent = { const setup = (events: readonly SerializedEvent[] = []) => { const isCompatible = async () => true; const storage: ActionStorage = new MemoryActionStorage(events); - const actions: ActionRegistry = new Map(); + const actions = new Map(); const uiActions = new UiActionsService({ actions, }); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts similarity index 88% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts index 97eb5b05fbbc21..73aff70332fc3c 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts @@ -1,31 +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. + * 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 { v4 as uuidv4 } from 'uuid'; import { Subscription } from 'rxjs'; import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { SerializedAction } from './types'; -import { TriggerContextMapping } from '../types'; -import { ActionDefinition } from './action'; +import { + UiActionsService, + UiActionsSerializedAction as SerializedAction, + TriggerContextMapping, + UiActionsActionDefinition as ActionDefinition, +} from '../../../../../src/plugins/ui_actions/public'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; -import { StateContainer, createStateContainer } from '../../../kibana_utils'; +import { StateContainer, createStateContainer } from '../../../../../src/plugins/kibana_utils'; const compareEvents = ( a: ReadonlyArray<{ eventId: string }>, diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts similarity index 75% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts index 636af076ea39f1..9f10eced43a659 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.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 { SerializedEvent } from './dynamic_action_storage'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts similarity index 77% rename from src/plugins/ui_actions/public/actions/dynamic_action_storage.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts index 28550a671782ef..92d2e8f0c3da77 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts @@ -1,26 +1,13 @@ /* - * 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. */ /* eslint-disable max-classes-per-file */ import { Observable, Subject } from 'rxjs'; -import { SerializedAction } from './types'; +import { UiActionsSerializedAction as SerializedAction } from '../../../../../src/plugins/ui_actions/public'; /** * Serialized representation of event-action pair, used to persist in storage. diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts new file mode 100644 index 00000000000000..a2f2818d297311 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/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 './dynamic_action_storage'; +export * from './dynamic_action_manager_state'; +export * from './dynamic_action_manager'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 55361105dcb0f0..9672fb10f9e164 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -26,3 +26,11 @@ export { Configurable as AdvancedUiActionsConfigurable, CollectConfigProps as AdvancedUiActionsCollectConfigProps, } from './util'; + +export { + AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, + DynamicActionManager as UiActionsEnhancedDynamicActionManager, + DynamicActionManagerParams as UiActionsEnhancedDynamicActionManagerParams, + DynamicActionManagerState as UiActionsEnhancedDynamicActionManagerState, + MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, +} from './dynamic_actions'; diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 30b3f3c080f493..fff0f67e14f90a 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -6,17 +6,20 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { - uiActions: UiActionsSetup; drilldowns: DrilldownsSetup; + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; } export interface StartDependencies { - uiActions: UiActionsStart; drilldowns: DrilldownsStart; + embeddable: EmbeddableStart; + uiActions: UiActionsStart; } // eslint-disable-next-line @@ -36,7 +39,7 @@ export class DashboardEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.drilldowns.bootstrap(core, plugins, { - enableDrilldowns: this.config.drilldowns.enabled, + enableDrilldowns: true, }); return {}; 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 31ee9e29938cb5..dcdefba04d882b 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 @@ -13,7 +13,7 @@ import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks' import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; -import { MockEmbeddable } from '../test_helpers'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); @@ -40,85 +40,91 @@ test('icon exists', () => { ); }); +interface CompatibilityParams { + isEdit?: boolean; + isValueClickTriggerSupported?: boolean; + isEmbeddableEnhanced?: boolean; +} + 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 - } - ), + async function assertCompatibility( + { + isEdit = true, + isValueClickTriggerSupported = true, + isEmbeddableEnhanced = true, + }: CompatibilityParams, + expectedResult: boolean = true + ): Promise { + let embeddable = new MockEmbeddable( + { id: '', viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW }, + { + supportedTriggers: (isValueClickTriggerSupported ? ['VALUE_CLICK_TRIGGER'] : []) as Array< + keyof TriggerContextMapping + >, + uiActions, + } + ); + + if (isEmbeddableEnhanced) { + embeddable = enhanceEmbeddable(embeddable); + } + + const result = await drilldownAction.isCompatible({ + embeddable, }); + + expect(result).toBe(expectedResult); } + const assertNonCompatibility = (params: CompatibilityParams) => + assertCompatibility(params, false); + 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); + await assertCompatibility({}); }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + test('not compatible if embeddable is not enhanced', async () => { + await assertNonCompatibility({ + isEmbeddableEnhanced: false, + }); }); test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: false, - }) - ).toBe(false); + await assertNonCompatibility({ + isValueClickTriggerSupported: false, + }); }); test('not compatible if in view mode', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: false, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + await assertNonCompatibility({ + isEdit: 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"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutCreateDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); + const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })); + await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable, }); + expect(spy).toBeCalled(); }); }); 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 00e74ea570a11a..d901bb8cf4e471 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 @@ -10,6 +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 { DrilldownsStart } from '../../../../../../drilldowns/public'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -37,7 +38,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; @@ -51,10 +52,12 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'create'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { 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 index a3f11eb976f907..06a36542582910 100644 --- 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 @@ -9,11 +9,24 @@ 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'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); +const uiActionsPlugin = uiActionsPluginMock.createPlugin(); +const uiActions = uiActionsPlugin.doStart(); + +uiActionsPlugin.setup.registerActionFactory({ + id: 'foo', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: () => true, + create: () => ({ + id: 'test', + execute: async () => {}, + }), +}); const actionParams: FlyoutEditDrilldownParams = { drilldowns: () => Promise.resolve(drilldowns), @@ -39,63 +52,93 @@ test('MenuItem exists', () => { }); describe('isCompatible', () => { - const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + function setupIsCompatible({ + isEdit = true, + isEmbeddableEnhanced = true, + }: { + isEdit?: boolean; + isEmbeddableEnhanced?: boolean; + } = {}) { + const action = new FlyoutEditDrilldownAction(actionParams); + const input = { + id: '', + viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW, + }; + const embeddable = new MockEmbeddable(input, { + uiActions, + }); + const context = { + embeddable: (isEmbeddableEnhanced + ? enhanceEmbeddable(embeddable, uiActions) + : embeddable) as EnhancedEmbeddable, + }; + + return { + action, + context, + }; + } + + test('not compatible if no drilldowns', async () => { + const { action, context } = setupIsCompatible(); + expect(await action.isCompatible(context)).toBe(false); + }); + + test('not compatible if embeddable is not enhanced', async () => { + const { action, context } = setupIsCompatible({ isEmbeddableEnhanced: false }); + expect(await action.isCompatible(context)).toBe(false); + }); + + describe('when has at least one drilldown', () => { + test('is compatible in edit mode', async () => { + const { action, context } = setupIsCompatible(); - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( + await context.embeddable.enhancements.dynamicActions.createEvent( { - id: '', - viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW, + config: {}, + factoryId: 'foo', + name: '', }, - { - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), + ['VALUE_CLICK_TRIGGER'] + ); + + expect(await action.isCompatible(context)).toBe(true); }); - } - // 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 in view mode', async () => { + const { action, context } = setupIsCompatible({ isEdit: false }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - }) - ).toBe(false); - }); + await context.embeddable.enhancements.dynamicActions.createEvent( + { + config: {}, + factoryId: 'foo', + name: '', + }, + ['VALUE_CLICK_TRIGGER'] + ); - test('not compatible if no drilldowns', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - }) - ).toBe(false); + expect(await action.isCompatible(context)).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"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable: enhanceEmbeddable(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 index 816b757592a721..23a6689bd8d9dc 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 @@ -15,6 +15,7 @@ import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins import { DrilldownsStart } from '../../../../../../drilldowns/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -42,16 +43,19 @@ export class FlyoutEditDrilldownAction implements ActionByType 0; + if (!isEnhancedEmbeddable(embeddable)) return false; + return embeddable.enhancements.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 { embeddable } = context; + + if (!isEnhancedEmbeddable(embeddable)) { + throw new Error( + 'Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction.' + ); } const handle = overlays.openFlyout( @@ -60,7 +64,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'manage'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { 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 index be693fadf9282c..c57bafc88e9787 100644 --- 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 @@ -8,8 +8,8 @@ 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 { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../advanced_ui_actions/public'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import '@testing-library/jest-dom'; afterEach(cleanup); @@ -20,8 +20,10 @@ test('', () => { ); 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 4f99fca511b073..ddcea0028409cd 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 @@ -6,15 +6,12 @@ import React from 'react'; 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'; +import { EnhancedEmbeddableContext } from '../../../../../../embeddable_enhanced/public'; +import { txtDisplayName } from './i18n'; -export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) => { - if (!context.embeddable.dynamicActions) - throw new Error('Flyout edit drillldown context menu item requires `dynamicActions`'); - - const { events } = useContainerState(context.embeddable.dynamicActions.state); +export const MenuItem: React.FC<{ context: EnhancedEmbeddableContext }> = ({ context }) => { + const { events } = useContainerState(context.embeddable.enhancements.dynamicActions.state); const count = events.length; return ( 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 index 9b156b0ba85b44..07751f383fe156 100644 --- 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 @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public/'; +import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../../../../../embeddable_enhanced/public'; +import { + UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, +} from '../../../../../advanced_ui_actions/public'; import { TriggerContextMapping, UiActionsStart, } from '../../../../../../../src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../../../../../src/plugins/ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { public readonly type = 'mock'; @@ -26,3 +32,17 @@ export class MockEmbeddable extends Embeddable { return this.triggers; } } + +export const enhanceEmbeddable = ( + embeddable: E, + uiActions: UiActionsStart = uiActionsPluginMock.createStartContract() +): EnhancedEmbeddable => { + (embeddable as EnhancedEmbeddable).enhancements = { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions, + }), + }; + return embeddable as EnhancedEmbeddable; +}; 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 4bdf03dff3531a..3c7089937488b3 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,10 +6,8 @@ import { CoreSetup } from 'src/core/public'; import { SetupDependencies } from '../../plugin'; -import { - CONTEXT_MENU_TRIGGER, - EmbeddableContext, -} from '../../../../../../src/plugins/embeddable/public'; +import { CONTEXT_MENU_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddableContext } from '../../../../embeddable_enhanced/public'; import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction, @@ -21,8 +19,8 @@ import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldow declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { - [OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext; - [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; + [OPEN_FLYOUT_ADD_DRILLDOWN]: EnhancedEmbeddableContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: EnhancedEmbeddableContext; } } 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 f22ccc2f26f02f..f4dcbf64d895e0 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,13 +9,13 @@ import useMountedState from 'react-use/lib/useMountedState'; import { AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, } 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'; import { - DynamicActionManager, UiActionsSerializedEvent, UiActionsSerializedAction, VALUE_CLICK_TRIGGER, 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 b8deaa8b842bc9..780c91eee56c39 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 @@ -6,8 +6,10 @@ import uuid from 'uuid'; import { - DynamicActionManager, - DynamicActionManagerState, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, + UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState, +} from '../../../../advanced_ui_actions/public'; +import { UiActionsSerializedAction, TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; diff --git a/x-pack/plugins/embeddable_enhanced/README.md b/x-pack/plugins/embeddable_enhanced/README.md new file mode 100644 index 00000000000000..a0be90731fdb02 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/README.md @@ -0,0 +1 @@ +# X-Pack part of `embeddable` plugin diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json new file mode 100644 index 00000000000000..9f63a55d5731d6 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "embeddableEnhanced", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["uiActions", "embeddable"] +} diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts similarity index 93% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 2dfa5fc30ef112..f2aa0f8ed8fdf9 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -1,28 +1,17 @@ /* - * 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 { Embeddable } from './embeddable'; -import { EmbeddableInput } from './i_embeddable'; -import { ViewMode } from '../types'; +import { + Embeddable, + EmbeddableInput, + ViewMode, +} from '../../../../../src/plugins/embeddable/public'; import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { UiActionsSerializedEvent } from '../../../../ui_actions/public'; -import { of } from '../../../../kibana_utils/public'; +import { UiActionsSerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts similarity index 54% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index fad5b4d535d6cd..ad4f82cc529b59 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -1,36 +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. + * 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 { - UiActionsAbstractActionStorage, - UiActionsSerializedEvent, -} from '../../../../ui_actions/public'; -import { Embeddable } from '..'; +import { UiActionsSerializedEvent as SerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage } from '../../../advanced_ui_actions/public'; +import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; -export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { - constructor(private readonly embbeddable: Embeddable) { +export class EmbeddableActionStorage extends AbstractActionStorage { + constructor(private readonly embbeddable: IEmbeddable) { super(); } - async create(event: UiActionsSerializedEvent) { + async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -45,9 +30,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async update(event: UiActionsSerializedEvent) { + async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -65,7 +50,7 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(event => eventId === event.eventId); if (index === -1) { @@ -81,9 +66,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async read(eventId: string): Promise { + async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const event = events.find(ev => eventId === ev.eventId); if (!event) { @@ -98,10 +83,10 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { private __list() { const input = this.embbeddable.getInput(); - return (input.events || []) as UiActionsSerializedEvent[]; + return (input.events || []) as SerializedEvent[]; } - async list(): Promise { + async list(): Promise { return this.__list(); } } diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts new file mode 100644 index 00000000000000..fabbc60a13f67a --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts @@ -0,0 +1,8 @@ +/* + * 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 './is_enhanced_embeddable'; +export * from './embeddable_action_storage'; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts new file mode 100644 index 00000000000000..f29430dc6a3deb --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.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 { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../types'; + +export const isEnhancedEmbeddable = ( + maybeEnhancedEmbeddable: E +): maybeEnhancedEmbeddable is EnhancedEmbeddable => + typeof (maybeEnhancedEmbeddable as EnhancedEmbeddable) + ?.enhancements?.dynamicActions === 'object'; diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts new file mode 100644 index 00000000000000..059acf96448207 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { PluginInitializerContext } from 'src/core/public'; +import { EmbeddableEnhancedPlugin } from './plugin'; + +export { + SetupContract as EmbeddableEnhancedSetupContract, + SetupDependencies as EmbeddableEnhancedSetupDependencies, + StartContract as EmbeddableEnhancedStartContract, + StartDependencies as EmbeddableEnhancedStartDependencies, +} from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new EmbeddableEnhancedPlugin(context); +} + +export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; +export { isEnhancedEmbeddable } from './embeddables'; diff --git a/x-pack/plugins/embeddable_enhanced/public/mocks.ts b/x-pack/plugins/embeddable_enhanced/public/mocks.ts new file mode 100644 index 00000000000000..d048d1248b6ff2 --- /dev/null +++ b/x-pack/plugins/embeddable_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 { EmbeddableEnhancedSetupContract, EmbeddableEnhancedStartContract } 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 embeddableEnhancedPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts new file mode 100644 index 00000000000000..5a1f07a4809372 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -0,0 +1,135 @@ +/* + * 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, PluginInitializerContext } from 'src/core/public'; +import { SavedObjectAttributes } from 'kibana/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { + EmbeddableFactory, + EmbeddableFactoryDefinition, + EmbeddableInput, + EmbeddableOutput, + EmbeddableSetup, + EmbeddableStart, + IEmbeddable, + defaultEmbeddableFactoryProvider, + EmbeddableContext, +} from '../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from './types'; +import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActions: UiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class EmbeddableEnhancedPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + private uiActions?: StartDependencies['uiActions']; + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + this.setCustomEmbeddableFactoryProvider(plugins); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + this.uiActions = plugins.uiActions; + + return {}; + } + + public stop() {} + + private setCustomEmbeddableFactoryProvider(plugins: SetupDependencies) { + plugins.embeddable.setCustomEmbeddableFactoryProvider( + < + I extends EmbeddableInput = EmbeddableInput, + O extends EmbeddableOutput = EmbeddableOutput, + E extends IEmbeddable = IEmbeddable, + T extends SavedObjectAttributes = SavedObjectAttributes + >( + def: EmbeddableFactoryDefinition + ): EmbeddableFactory => { + const factory: EmbeddableFactory = defaultEmbeddableFactoryProvider( + def + ); + return { + ...factory, + create: async (...args) => { + const embeddable = await factory.create(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + createFromSavedObject: async (...args) => { + const embeddable = await factory.createFromSavedObject(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + }; + } + ); + } + + private enhanceEmbeddableWithDynamicActions( + embeddable: E + ): EnhancedEmbeddable { + const enhancedEmbeddable = embeddable as EnhancedEmbeddable; + + const storage = new EmbeddableActionStorage(embeddable); + const dynamicActions = new DynamicActionManager({ + isCompatible: async (context: unknown) => + (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId, + storage, + uiActions: this.uiActions!, + }); + + dynamicActions.start().catch(error => { + /* eslint-disable */ + console.log('Failed to start embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + + const stop = () => { + dynamicActions.stop().catch(error => { + /* eslint-disable */ + console.log('Failed to stop embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + }; + + embeddable.getInput$().subscribe({ + next: () => { + storage.reload$.next(); + }, + error: stop, + complete: stop, + }); + + enhancedEmbeddable.enhancements = { + ...enhancedEmbeddable.enhancements, + dynamicActions, + }; + + return enhancedEmbeddable; + } +} diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts new file mode 100644 index 00000000000000..924605be332b21 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/types.ts @@ -0,0 +1,21 @@ +/* + * 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 { IEmbeddable } from '../../../../src/plugins/embeddable/public'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export type EnhancedEmbeddable = E & { + enhancements: { + /** + * Default implementation of dynamic action manager for embeddables. + */ + dynamicActions: DynamicActionManager; + }; +}; + +export interface EnhancedEmbeddableContext { + embeddable: EnhancedEmbeddable; +} From bcd49ea05435e6953319d27a115e69f4e821e72a Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 10 Apr 2020 12:34:06 +0200 Subject: [PATCH 085/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20actio?= =?UTF-8?q?n=20factories=20to=20x-pack=20(#63190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 move action factories to x-pack * fix: 🐛 use correct plugin embeddable deps * test: 💍 fix Jest test after refactor --- src/plugins/embeddable/public/index.ts | 7 +- src/plugins/embeddable/public/mocks.ts | 15 ++-- .../actions/action_factory_definition.ts | 46 ------------ .../ui_actions/public/actions/index.ts | 3 - .../ui_actions/public/actions/types.ts | 30 -------- src/plugins/ui_actions/public/index.ts | 3 - src/plugins/ui_actions/public/mocks.ts | 4 - src/plugins/ui_actions/public/plugin.ts | 1 - .../public/service/ui_actions_service.test.ts | 68 +---------------- .../public/service/ui_actions_service.ts | 53 +------------- src/plugins/ui_actions/public/types.ts | 2 - .../action_wizard/action_wizard.tsx | 2 +- .../components/action_wizard/test_data.tsx | 2 +- .../public/dynamic_actions}/action_factory.ts | 27 ++----- .../action_factory_definition.ts | 36 +++++++++ .../dynamic_action_manager.test.ts | 12 +-- .../dynamic_actions/dynamic_action_manager.ts | 8 +- .../dynamic_action_manager_state.ts | 2 +- .../dynamic_actions/dynamic_action_storage.ts | 11 +-- .../public/dynamic_actions/index.ts | 3 + .../public/dynamic_actions/types.ts | 20 +++++ .../advanced_ui_actions/public/index.ts | 4 +- .../advanced_ui_actions/public/mocks.ts | 73 +++++++++++++++++++ .../advanced_ui_actions/public/plugin.ts | 16 +++- .../action_factory_service/action_factory.ts | 11 --- .../action_factory_definition.ts | 11 --- .../services/action_factory_service/index.ts | 8 -- .../public/services/index.ts | 2 +- .../ui_actions_service_enhancements.test.ts | 70 ++++++++++++++++++ .../ui_actions_service_enhancements.ts | 57 +++++++++++++++ .../advanced_ui_actions/public/types.ts | 3 + .../flyout_edit_drilldown.test.tsx | 4 +- .../drilldowns/actions/test_helpers.ts | 5 +- .../connected_flyout_manage_drilldowns.tsx | 10 +-- .../test_data.ts | 10 +-- .../public/services/drilldown_service.ts | 6 +- .../plugins/embeddable_enhanced/kibana.json | 2 +- .../embeddable_action_storage.test.ts | 56 +++++++------- .../embeddables/embeddable_action_storage.ts | 6 +- .../embeddable_enhanced/public/plugin.ts | 15 ++-- 40 files changed, 379 insertions(+), 345 deletions(-) delete mode 100644 src/plugins/ui_actions/public/actions/action_factory_definition.ts delete mode 100644 src/plugins/ui_actions/public/actions/types.ts rename {src/plugins/ui_actions/public/actions => x-pack/plugins/advanced_ui_actions/public/dynamic_actions}/action_factory.ts (65%) create mode 100644 x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/dynamic_actions/types.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/mocks.ts delete mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.ts delete mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.ts delete mode 100644 x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.test.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 3f671b520fb2ef..be8548f0d9d736 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -68,4 +68,9 @@ export function plugin(initializerContext: PluginInitializerContext) { return new EmbeddablePublicPlugin(initializerContext); } -export { EmbeddableSetup, EmbeddableStart } from './plugin'; +export { + EmbeddableSetup, + EmbeddableStart, + EmbeddableSetupDependencies, + EmbeddableStartDependencies, +} from './plugin'; diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts index 65b15f3a7614fb..f5487c381cfcb3 100644 --- a/src/plugins/embeddable/public/mocks.ts +++ b/src/plugins/embeddable/public/mocks.ts @@ -16,7 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { EmbeddableStart, EmbeddableSetup } from '.'; +import { + EmbeddableStart, + EmbeddableSetup, + EmbeddableSetupDependencies, + EmbeddableStartDependencies, +} from '.'; import { EmbeddablePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; @@ -45,14 +50,14 @@ const createStartContract = (): Start => { return startContract; }; -const createInstance = () => { +const createInstance = (setupPlugins: Partial = {}) => { const plugin = new EmbeddablePublicPlugin({} as any); const setup = plugin.setup(coreMock.createSetup(), { - uiActions: uiActionsPluginMock.createSetupContract(), + uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), }); - const doStart = () => + const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { - uiActions: uiActionsPluginMock.createStartContract(), + uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), }); return { diff --git a/src/plugins/ui_actions/public/actions/action_factory_definition.ts b/src/plugins/ui_actions/public/actions/action_factory_definition.ts deleted file mode 100644 index 7ac94a41e70767..00000000000000 --- a/src/plugins/ui_actions/public/actions/action_factory_definition.ts +++ /dev/null @@ -1,46 +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 { ActionDefinition } from './action'; -import { Presentable, Configurable } from '../util'; -import { SerializedAction } from './types'; - -/** - * 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 type 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( - serializedAction: Omit, 'factoryId'> - ): ActionDefinition; -} diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 0b0e60b3cf75ce..88e42ff2ec1131 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -19,8 +19,5 @@ 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 './types'; diff --git a/src/plugins/ui_actions/public/actions/types.ts b/src/plugins/ui_actions/public/actions/types.ts deleted file mode 100644 index d29e97eea532f5..00000000000000 --- a/src/plugins/ui_actions/public/actions/types.ts +++ /dev/null @@ -1,30 +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 interface SerializedAction { - readonly factoryId: string; - readonly name: string; - readonly config: Config; -} - -export interface SerializedEvent { - eventId: string; - triggers: string[]; - action: SerializedAction; -} diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 4f00ac4a26fc39..19c9b0a41a0935 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -29,12 +29,9 @@ export { UiActionsServiceParams, UiActionsService } from './service'; export { Action, ActionDefinition as UiActionsActionDefinition, - ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, createAction, IncompatibleActionError, - SerializedAction as UiActionsSerializedAction, - SerializedEvent as UiActionsSerializedEvent, } from './actions'; export { buildContextMenuForActions } from './context_menu'; export { diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 4de38eb5421e98..3522ac4941ba0a 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -32,7 +32,6 @@ const createSetupContract = (): Setup => { attachAction: jest.fn(), detachAction: jest.fn(), registerAction: jest.fn(), - registerActionFactory: jest.fn(), registerTrigger: jest.fn(), unregisterAction: jest.fn(), }; @@ -49,13 +48,10 @@ const createStartContract = (): Start => { 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(), registerAction: jest.fn(), - registerActionFactory: jest.fn(), registerTrigger: jest.fn(), }; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index 88a5cb04eac6f8..71148656cbb164 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -27,7 +27,6 @@ export type UiActionsSetup = Pick< | 'attachAction' | 'detachAction' | 'registerAction' - | 'registerActionFactory' | 'registerTrigger' | 'unregisterAction' >; 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 41e2b57d53dd85..45a1bdffa52adf 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,13 +18,7 @@ */ import { UiActionsService } from './ui_actions_service'; -import { - Action, - ActionInternal, - createAction, - ActionFactoryDefinition, - ActionFactory, -} 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'; @@ -497,64 +491,4 @@ describe('UiActionsService', () => { ); }); }); - - describe('action factories', () => { - const factoryDefinition1: ActionFactoryDefinition = { - id: 'test-factory-1', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: () => true, - create: () => ({} as any), - }; - const factoryDefinition2: ActionFactoryDefinition = { - id: 'test-factory-2', - CollectConfig: {} as any, - createConfig: () => ({}), - isConfigValid: () => true, - 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 8bd3bb34fbbd81..f00877f812b24a 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -24,17 +24,8 @@ import { TriggerId, TriggerContextMapping, ActionType, - ActionFactoryRegistry, } from '../types'; -import { - ActionInternal, - Action, - ActionByType, - ActionFactory, - ActionDefinition, - ActionFactoryDefinition, - ActionContext, -} from '../actions'; +import { ActionInternal, Action, ActionByType, ActionDefinition, ActionContext } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; import { TriggerContract } from '../triggers/trigger_contract'; @@ -47,25 +38,21 @@ 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) => { @@ -215,7 +202,6 @@ export class UiActionsService { this.actions.clear(); this.triggers.clear(); this.triggerToActions.clear(); - this.actionFactories.clear(); }; /** @@ -235,41 +221,4 @@ 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 = < - 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); - - this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory); - }; - - public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { - 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. - */ - 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 2cb4a8f26a879d..ddfdad7b9716c3 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -19,7 +19,6 @@ import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; -import { ActionFactory } from './actions'; import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public'; import { Filter } from '../../data/public'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; @@ -27,7 +26,6 @@ import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from 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/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 846f6d41eb30d1..ac6aead489edd5 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 { ActionFactory } from '../../services'; +import { ActionFactory } from '../../dynamic_actions'; type ActionBaseConfig = object; type ActionFactoryBaseContext = object; 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 167cb130fdb4aa..102aee1878bc5d 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, ActionFactory } from '../../services'; +import { ActionFactoryDefinition, ActionFactory } from '../../dynamic_actions'; import { CollectConfigProps } from '../../util'; type ActionBaseConfig = object; diff --git a/src/plugins/ui_actions/public/actions/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts similarity index 65% rename from src/plugins/ui_actions/public/actions/action_factory.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts index bc0ec844d00f5f..cf2e16e1805e7e 100644 --- a/src/plugins/ui_actions/public/actions/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts @@ -1,25 +1,14 @@ /* - * 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 { uiToReactComponent } from '../../../kibana_react/public'; -import { Presentable } from '../util/presentable'; -import { ActionDefinition } from './action'; +import { uiToReactComponent } from '../../../../../src/plugins/kibana_react/public'; +import { + UiActionsActionDefinition as ActionDefinition, + UiActionsPresentable as Presentable, +} from '../../../../../src/plugins/ui_actions/public'; import { ActionFactoryDefinition } from './action_factory_definition'; import { Configurable } from '../util'; import { SerializedAction } from './types'; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts new file mode 100644 index 00000000000000..7cb21ee29f70ce --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.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 { + UiActionsActionDefinition as ActionDefinition, + UiActionsPresentable as Presentable, +} from '../../../../../src/plugins/ui_actions/public'; +import { Configurable } from '../util'; +import { SerializedAction } from './types'; + +/** + * 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 type 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( + serializedAction: Omit, 'factoryId'> + ): ActionDefinition; +} diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts index e4bd73558b3578..9e9712304af3f6 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts @@ -5,14 +5,15 @@ */ import { DynamicActionManager } from './dynamic_action_manager'; -import { ActionStorage, MemoryActionStorage, SerializedEvent } from './dynamic_action_storage'; +import { ActionStorage, MemoryActionStorage } from './dynamic_action_storage'; import { UiActionsService, - UiActionsActionFactoryDefinition as ActionFactoryDefinition, - UiActionsSerializedAction as SerializedAction, UiActionsActionInternal as ActionInternal, } from '../../../../../src/plugins/ui_actions/public'; import { of } from '../../../../../src/plugins/kibana_utils'; +import { UiActionsServiceEnhancements } from '../services'; +import { ActionFactoryDefinition } from './action_factory_definition'; +import { SerializedAction, SerializedEvent } from './types'; const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -75,10 +76,11 @@ const setup = (events: readonly SerializedEvent[] = []) => { const uiActions = new UiActionsService({ actions, }); + const uiActionsEnhancements = new UiActionsServiceEnhancements(); const manager = new DynamicActionManager({ isCompatible, storage, - uiActions, + uiActions: { ...uiActions, ...uiActionsEnhancements }, }); uiActions.registerTrigger({ @@ -89,7 +91,7 @@ const setup = (events: readonly SerializedEvent[] = []) => { isCompatible, actions, storage, - uiActions, + uiActions: { ...uiActions, ...uiActionsEnhancements }, manager, }; }; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts index 73aff70332fc3c..df214bfe80cc70 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts @@ -6,15 +6,15 @@ import { v4 as uuidv4 } from 'uuid'; import { Subscription } from 'rxjs'; -import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; +import { ActionStorage } from './dynamic_action_storage'; import { - UiActionsService, - UiActionsSerializedAction as SerializedAction, TriggerContextMapping, UiActionsActionDefinition as ActionDefinition, } from '../../../../../src/plugins/ui_actions/public'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; import { StateContainer, createStateContainer } from '../../../../../src/plugins/kibana_utils'; +import { StartContract } from '../plugin'; +import { SerializedAction, SerializedEvent } from './types'; const compareEvents = ( a: ReadonlyArray<{ eventId: string }>, @@ -30,7 +30,7 @@ export type DynamicActionManagerState = State; export interface DynamicActionManagerParams { storage: ActionStorage; uiActions: Pick< - UiActionsService, + StartContract, 'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' >; isCompatible: (context: C) => Promise; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts index 9f10eced43a659..61e8604baa9133 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SerializedEvent } from './dynamic_action_storage'; +import { SerializedEvent } from './types'; /** * This interface represents the state of @type {DynamicActionManager} at any diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts index 92d2e8f0c3da77..e40441e67f033e 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts @@ -7,16 +7,7 @@ /* eslint-disable max-classes-per-file */ import { Observable, Subject } from 'rxjs'; -import { UiActionsSerializedAction as SerializedAction } from '../../../../../src/plugins/ui_actions/public'; - -/** - * Serialized representation of event-action pair, used to persist in storage. - */ -export interface SerializedEvent { - eventId: string; - triggers: string[]; - action: SerializedAction; -} +import { SerializedEvent } from './types'; /** * This CRUD interface needs to be implemented by dynamic action users if they diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts index a2f2818d297311..bb37cf5e69535b 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './types'; +export * from './action_factory'; +export * from './action_factory_definition'; export * from './dynamic_action_storage'; export * from './dynamic_action_manager_state'; export * from './dynamic_action_manager'; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/types.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/types.ts new file mode 100644 index 00000000000000..9148d1ec7055aa --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/types.ts @@ -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. + */ + +export interface SerializedAction { + readonly factoryId: string; + readonly name: string; + readonly config: Config; +} + +/** + * Serialized representation of a triggers-action pair, used to persist in storage. + */ +export interface SerializedEvent { + eventId: string; + triggers: string[]; + action: SerializedAction; +} diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 9672fb10f9e164..79fb03e9adbe63 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -21,7 +21,9 @@ export { ActionWizard } from './components'; export { ActionFactoryDefinition as AdvancedUiActionsActionFactoryDefinition, ActionFactory as AdvancedUiActionsActionFactory, -} from './services/action_factory_service'; + SerializedAction as UiActionsEnhancedSerializedAction, + SerializedEvent as UiActionsEnhancedSerializedEvent, +} from './dynamic_actions'; export { Configurable as AdvancedUiActionsConfigurable, CollectConfigProps as AdvancedUiActionsCollectConfigProps, diff --git a/x-pack/plugins/advanced_ui_actions/public/mocks.ts b/x-pack/plugins/advanced_ui_actions/public/mocks.ts new file mode 100644 index 00000000000000..39d88b017ca074 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/mocks.ts @@ -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 { CoreSetup, CoreStart } from '../../../../src/core/public'; +import { coreMock } from '../../../../src/core/public/mocks'; +import { uiActionsPluginMock } from '../../../../src/plugins/ui_actions/public/mocks'; +import { embeddablePluginMock } from '../../../../src/plugins/embeddable/public/mocks'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '.'; +import { plugin as pluginInitializer } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + ...uiActionsPluginMock.createSetupContract(), + registerActionFactory: jest.fn(), + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = { + ...uiActionsPluginMock.createStartContract(), + getActionFactories: jest.fn(), + getActionFactory: jest.fn(), + }; + + return startContract; +}; + +const createPlugin = ( + coreSetup: CoreSetup = coreMock.createSetup(), + coreStart: CoreStart = coreMock.createStart() +) => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const uiActions = uiActionsPluginMock.createPlugin(); + const embeddable = embeddablePluginMock.createInstance({ + uiActions: uiActions.setup, + }); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = plugin.setup(coreSetup, { + uiActions: uiActions.setup, + embeddable: embeddable.setup, + }); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: (anotherCoreStart: CoreStart = coreStart) => { + const uiActionsStart = uiActions.doStart(); + const embeddableStart = embeddable.doStart({ + uiActions: uiActionsStart, + }); + return plugin.start(anotherCoreStart, { + uiActions: uiActionsStart, + embeddable: embeddableStart, + }); + }, + }; +}; + +export const uiActionsEnhancedPluginMock = { + createSetupContract, + createStartContract, + createPlugin, +}; diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index 71ea72499d87fd..cb2bbdba0a313b 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 { UiActionsServiceEnhancements } from './services'; interface SetupDependencies { embeddable: EmbeddableSetup; // Embeddable are needed because they register basic triggers/actions. @@ -41,10 +42,13 @@ interface StartDependencies { uiActions: UiActionsStart; } -// 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 {} +export interface SetupContract + extends UiActionsSetup, + Pick {} + +export interface StartContract + extends UiActionsStart, + Pick {} declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -55,11 +59,14 @@ declare module '../../../../src/plugins/ui_actions/public' { export class AdvancedUiActionsPublicPlugin implements Plugin { + private readonly enhancements = new UiActionsServiceEnhancements(); + constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { uiActions }: SetupDependencies): SetupContract { return { ...uiActions, + ...this.enhancements, }; } @@ -83,6 +90,7 @@ export class AdvancedUiActionsPublicPlugin return { ...uiActions, + ...this.enhancements, }; } 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 deleted file mode 100644 index 66e2a4eafa880b..00000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory.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. - */ - -/* eslint-disable */ - -export { - 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 deleted file mode 100644 index f8669a4bf813f3..00000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/action_factory_definition.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. - */ - -/* eslint-disable */ - -export { - ActionFactoryDefinition -} from '../../../../../../src/plugins/ui_actions/public/actions/action_factory_definition'; 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 deleted file mode 100644 index db5bb3aa62a163..00000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/services/action_factory_service/index.ts +++ /dev/null @@ -1,8 +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 './action_factory_definition'; -export * from './action_factory'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/index.ts b/x-pack/plugins/advanced_ui_actions/public/services/index.ts index 0f8b4c8d8f409e..71a3429800c431 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/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 './action_factory_service'; +export * from './ui_actions_service_enhancements'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.test.ts b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.test.ts new file mode 100644 index 00000000000000..3137e35a2fe476 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { UiActionsServiceEnhancements } from './ui_actions_service_enhancements'; +import { ActionFactoryDefinition, ActionFactory } from '../dynamic_actions'; + +describe('UiActionsService', () => { + describe('action factories', () => { + const factoryDefinition1: ActionFactoryDefinition = { + id: 'test-factory-1', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: () => true, + create: () => ({} as any), + }; + const factoryDefinition2: ActionFactoryDefinition = { + id: 'test-factory-2', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: () => true, + create: () => ({} as any), + }; + + test('.getActionFactories() returns empty array if no action factories registered', () => { + const service = new UiActionsServiceEnhancements(); + + const factories = service.getActionFactories(); + + expect(factories).toEqual([]); + }); + + test('can register and retrieve an action factory', () => { + const service = new UiActionsServiceEnhancements(); + + 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 UiActionsServiceEnhancements(); + + 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 UiActionsServiceEnhancements(); + + service.registerActionFactory(factoryDefinition1); + + expect(() => service.getActionFactory('UNKNOWN_ID')).toThrowError( + 'Action factory [actionFactoryId = UNKNOWN_ID] does not exist.' + ); + }); + }); +}); diff --git a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts new file mode 100644 index 00000000000000..87bbc803964931 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts @@ -0,0 +1,57 @@ +/* + * 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 { ActionFactoryRegistry } from '../types'; +import { ActionFactory, ActionFactoryDefinition } from '../dynamic_actions'; + +export interface UiActionsServiceEnhancementsParams { + readonly actionFactories?: ActionFactoryRegistry; +} + +export class UiActionsServiceEnhancements { + protected readonly actionFactories: ActionFactoryRegistry; + + constructor({ actionFactories = new Map() }: UiActionsServiceEnhancementsParams = {}) { + this.actionFactories = actionFactories; + } + + /** + * Register an action factory. Action factories are used to configure and + * serialize/deserialize dynamic actions. + */ + 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); + + this.actionFactories.set(actionFactory.id, actionFactory as ActionFactory); + }; + + public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { + 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. + */ + public readonly getActionFactories = (): ActionFactory[] => { + return [...this.actionFactories.values()]; + }; +} diff --git a/x-pack/plugins/advanced_ui_actions/public/types.ts b/x-pack/plugins/advanced_ui_actions/public/types.ts index 313b09535b196a..5c960192dcaff9 100644 --- a/x-pack/plugins/advanced_ui_actions/public/types.ts +++ b/x-pack/plugins/advanced_ui_actions/public/types.ts @@ -5,6 +5,7 @@ */ import { KibanaReactOverlays } from '../../../../src/plugins/kibana_react/public'; +import { ActionFactory } from './dynamic_actions'; export interface CommonlyUsedRange { from: string; @@ -13,3 +14,5 @@ export interface CommonlyUsedRange { } export type OpenModal = KibanaReactOverlays['openModal']; + +export type ActionFactoryRegistry = Map; 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 index 06a36542582910..e44f363f411809 100644 --- 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 @@ -8,13 +8,13 @@ import { FlyoutEditDrilldownAction, FlyoutEditDrilldownParams } from './flyout_e 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 { uiActionsEnhancedPluginMock } from '../../../../../../advanced_ui_actions/public/mocks'; import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActionsPlugin = uiActionsPluginMock.createPlugin(); +const uiActionsPlugin = uiActionsEnhancedPluginMock.createPlugin(); const uiActions = uiActionsPlugin.doStart(); uiActionsPlugin.setup.registerActionFactory({ 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 index 07751f383fe156..47685f43888766 100644 --- 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 @@ -9,12 +9,13 @@ import { EnhancedEmbeddable } from '../../../../../embeddable_enhanced/public'; import { UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, UiActionsEnhancedDynamicActionManager as DynamicActionManager, + AdvancedUiActionsStart, } from '../../../../../advanced_ui_actions/public'; import { TriggerContextMapping, UiActionsStart, } from '../../../../../../../src/plugins/ui_actions/public'; -import { uiActionsPluginMock } from '../../../../../../../src/plugins/ui_actions/public/mocks'; +import { uiActionsEnhancedPluginMock } from '../../../../../advanced_ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { public readonly type = 'mock'; @@ -35,7 +36,7 @@ export class MockEmbeddable extends Embeddable { export const enhanceEmbeddable = ( embeddable: E, - uiActions: UiActionsStart = uiActionsPluginMock.createStartContract() + uiActions: AdvancedUiActionsStart = uiActionsEnhancedPluginMock.createStartContract() ): EnhancedEmbeddable => { (embeddable as EnhancedEmbeddable).enhancements = { dynamicActions: new DynamicActionManager({ 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 f4dcbf64d895e0..c8a67a20aa5c16 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 @@ -10,14 +10,14 @@ import { AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, UiActionsEnhancedDynamicActionManager as DynamicActionManager, + UiActionsEnhancedSerializedAction, + UiActionsEnhancedSerializedEvent, } 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'; import { - UiActionsSerializedEvent, - UiActionsSerializedAction, VALUE_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, TriggerContextMapping, @@ -132,7 +132,7 @@ export function createFlyoutManageDrilldowns({ * Maps drilldown to list item view model */ function mapToDrilldownToDrilldownListItem( - drilldown: UiActionsSerializedEvent + drilldown: UiActionsEnhancedSerializedEvent ): DrilldownListItem { const actionFactory = allActionFactoriesById[drilldown.action.factoryId]; return { @@ -284,7 +284,7 @@ function useDrilldownsStateManager( } async function createDrilldown( - action: UiActionsSerializedAction, + action: UiActionsEnhancedSerializedAction, selectedTriggers: Array ) { await run(async () => { @@ -298,7 +298,7 @@ function useDrilldownsStateManager( async function editDrilldown( drilldownId: string, - action: UiActionsSerializedAction, + action: UiActionsEnhancedSerializedAction, selectedTriggers: Array ) { await run(async () => { 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 780c91eee56c39..47a04222286cbf 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 @@ -8,11 +8,9 @@ import uuid from 'uuid'; import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState, + UiActionsEnhancedSerializedAction, } from '../../../../advanced_ui_actions/public'; -import { - UiActionsSerializedAction, - TriggerContextMapping, -} from '../../../../../../src/plugins/ui_actions/public'; +import { TriggerContextMapping } from '../../../../../../src/plugins/ui_actions/public'; import { createStateContainer } from '../../../../../../src/plugins/kibana_utils/common'; class MockDynamicActionManager implements PublicMethodsOf { @@ -31,7 +29,7 @@ class MockDynamicActionManager implements PublicMethodsOf } async createEvent( - action: UiActionsSerializedAction, + action: UiActionsEnhancedSerializedAction, triggers: Array ) { const event = { @@ -62,7 +60,7 @@ class MockDynamicActionManager implements PublicMethodsOf async updateEvent( eventId: string, - action: UiActionsSerializedAction, + action: UiActionsEnhancedSerializedAction, triggers: Array ) { const state = this.state.get(); diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index bfbe514d460955..cafaa945683546 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -5,9 +5,11 @@ */ import { CoreSetup } from 'src/core/public'; -import { AdvancedUiActionsSetup } from '../../../advanced_ui_actions/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition, +} from '../../../advanced_ui_actions/public'; import { DrilldownDefinition, DrilldownFactoryContext } from '../types'; -import { UiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../../../../src/plugins/ui_actions/public'; export interface DrilldownServiceSetupDeps { advancedUiActions: AdvancedUiActionsSetup; diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json index 9f63a55d5731d6..780a1d5d89870e 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.json +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["uiActions", "embeddable"] + "requiredPlugins": ["embeddable", "advancedUiActions"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index f2aa0f8ed8fdf9..4eeda32a2de76e 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -10,7 +10,7 @@ import { ViewMode, } from '../../../../../src/plugins/embeddable/public'; import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { UiActionsSerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { UiActionsEnhancedSerializedEvent } from '../../../advanced_ui_actions/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; class TestEmbeddable extends Embeddable { @@ -32,7 +32,7 @@ describe('EmbeddableActionStorage', () => { test('can add event to embeddable', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -50,7 +50,7 @@ describe('EmbeddableActionStorage', () => { test('does not merge .getInput() into .updateInput()', async () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -68,17 +68,17 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event3: UiActionsSerializedEvent = { + const event3: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID3', triggers: ['TRIGGER-ID'], action: {} as any, @@ -102,7 +102,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: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -129,14 +129,14 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: { @@ -155,28 +155,28 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: { name: 'bar', } as any, }; - const event22: UiActionsSerializedEvent = { + const event22: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: { name: 'baz', } as any, }; - const event3: UiActionsSerializedEvent = { + const event3: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID3', triggers: ['TRIGGER-ID'], action: { @@ -206,7 +206,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -224,12 +224,12 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: {} as any, @@ -256,7 +256,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -273,21 +273,21 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: { name: 'foo', } as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: { name: 'bar', } as any, }; - const event3: UiActionsSerializedEvent = { + const event3: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID3', triggers: ['TRIGGER-ID'], action: { @@ -334,7 +334,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -362,7 +362,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -390,7 +390,7 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event: UiActionsSerializedEvent = { + const event: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID', triggers: ['TRIGGER-ID'], action: {} as any, @@ -409,17 +409,17 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event3: UiActionsSerializedEvent = { + const event3: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID3', triggers: ['TRIGGER-ID'], action: {} as any, @@ -509,13 +509,13 @@ describe('EmbeddableActionStorage', () => { const embeddable = new TestEmbeddable(); const storage = new EmbeddableActionStorage(embeddable); - const event1: UiActionsSerializedEvent = { + const event1: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID1', triggers: ['TRIGGER-ID'], action: {} as any, }; - const event2: UiActionsSerializedEvent = { + const event2: UiActionsEnhancedSerializedEvent = { eventId: 'EVENT_ID2', triggers: ['TRIGGER-ID'], action: {} as any, diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index ad4f82cc529b59..d02d39b5b172c6 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UiActionsSerializedEvent as SerializedEvent } from '../../../../../src/plugins/ui_actions/public'; -import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage } from '../../../advanced_ui_actions/public'; +import { + UiActionsEnhancedAbstractActionStorage as AbstractActionStorage, + UiActionsEnhancedSerializedEvent as SerializedEvent, +} from '../../../advanced_ui_actions/public'; import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; export class EmbeddableActionStorage extends AbstractActionStorage { diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 5a1f07a4809372..a7a56d2c0d78d0 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -6,7 +6,6 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { SavedObjectAttributes } from 'kibana/public'; -import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { EmbeddableFactory, EmbeddableFactoryDefinition, @@ -20,16 +19,20 @@ import { } from '../../../../src/plugins/embeddable/public'; import { EnhancedEmbeddable } from './types'; import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; -import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; +import { + UiActionsEnhancedDynamicActionManager as DynamicActionManager, + AdvancedUiActionsSetup, + AdvancedUiActionsStart, +} from '../../advanced_ui_actions/public'; export interface SetupDependencies { embeddable: EmbeddableSetup; - uiActions: UiActionsSetup; + advancedUiActions: AdvancedUiActionsSetup; } export interface StartDependencies { embeddable: EmbeddableStart; - uiActions: UiActionsStart; + advancedUiActions: AdvancedUiActionsStart; } // eslint-disable-next-line @@ -42,7 +45,7 @@ export class EmbeddableEnhancedPlugin implements Plugin { constructor(protected readonly context: PluginInitializerContext) {} - private uiActions?: StartDependencies['uiActions']; + private uiActions?: StartDependencies['advancedUiActions']; public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); @@ -51,7 +54,7 @@ export class EmbeddableEnhancedPlugin } public start(core: CoreStart, plugins: StartDependencies): StartContract { - this.uiActions = plugins.uiActions; + this.uiActions = plugins.advancedUiActions; return {}; } From 1824c20849e7ae4509b99e6da81669dca3e77543 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 14 Apr 2020 21:39:36 +0200 Subject: [PATCH 086/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20kibana?= =?UTF-8?q?.yml=20flag=20(#62441)?= 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/public/plugin.ts | 5 +--- .../dashboard_enhanced/server/config.ts | 23 ------------------- .../dashboard_enhanced/server/index.ts | 12 ---------- 4 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 x-pack/plugins/dashboard_enhanced/server/config.ts delete 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 acbca5c33295c0..942b01e2b2f3ef 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": true, + "server": false, "ui": true, "requiredPlugins": ["uiActions", "embeddable", "dashboard", "drilldowns"], "configPath": ["xpack", "dashboardEnhanced"] diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index fff0f67e14f90a..e0f2aff7c70a82 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -31,11 +31,8 @@ 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(); - } + constructor(protected readonly context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.drilldowns.bootstrap(core, plugins, { diff --git a/x-pack/plugins/dashboard_enhanced/server/config.ts b/x-pack/plugins/dashboard_enhanced/server/config.ts deleted file mode 100644 index b75c95d5f8832e..00000000000000 --- a/x-pack/plugins/dashboard_enhanced/server/config.ts +++ /dev/null @@ -1,23 +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 { 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 deleted file mode 100644 index e361b9fb075eda..00000000000000 --- a/x-pack/plugins/dashboard_enhanced/server/index.ts +++ /dev/null @@ -1,12 +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 { config } from './config'; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); From 379ec52ee02cd648ba2d1ceec224336f8bf408eb Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Wed, 15 Apr 2020 17:27:45 +0200 Subject: [PATCH 087/129] Panel top right (#63466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add PANEL_NOTIFICATION_TRIGGER * feat: 🎸 add PanelNotificationsAction action * test: 💍 add PanelNotificationsAction unit tests * refactor: 💡 revert addTriggerAction() change * style: 💄 remove unused import --- src/plugins/embeddable/public/bootstrap.ts | 4 + src/plugins/embeddable/public/index.ts | 2 + .../public/lib/panel/embeddable_panel.tsx | 45 ++++++----- .../lib/panel/panel_header/panel_header.tsx | 25 +++++-- .../public/lib/triggers/triggers.ts | 9 ++- .../public/actions/index.ts | 7 ++ .../panel_notifications_action.test.ts | 75 +++++++++++++++++++ .../actions/panel_notifications_action.ts | 34 +++++++++ .../embeddable_enhanced/public/plugin.ts | 14 +++- 9 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/index.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts create mode 100644 x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index c8c4f0b95c4587..33cf210763b100 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -31,12 +31,15 @@ import { ACTION_EDIT_PANEL, FilterActionContext, ACTION_APPLY_FILTER, + panelNotificationTrigger, + PANEL_NOTIFICATION_TRIGGER, } from './lib'; declare module '../../ui_actions/public' { export interface TriggerContextMapping { [CONTEXT_MENU_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; + [PANEL_NOTIFICATION_TRIGGER]: EmbeddableContext; } export interface ActionContextMapping { @@ -56,6 +59,7 @@ declare module '../../ui_actions/public' { export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); uiActions.registerTrigger(panelBadgeTrigger); + uiActions.registerTrigger(panelNotificationTrigger); const actionApplyFilter = createFilterAction(); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index be8548f0d9d736..7daa6469285513 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -57,6 +57,8 @@ export { OutputSpec, PANEL_BADGE_TRIGGER, panelBadgeTrigger, + PANEL_NOTIFICATION_TRIGGER, + panelNotificationTrigger, PanelNotFoundError, PanelState, PropertySpec, diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index d4d23874cfb19d..7d3aa009d2bc77 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -25,7 +25,12 @@ 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, + PANEL_BADGE_TRIGGER, + PANEL_NOTIFICATION_TRIGGER, + EmbeddableContext, +} from '../triggers'; import { IEmbeddable } from '../embeddables/i_embeddable'; import { ViewMode } from '../types'; @@ -65,14 +70,13 @@ interface State { hidePanelTitles: boolean; closeContextMenu: boolean; badges: Array>; - eventCount?: number; + notifications: Array>; } export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; private subscription?: Subscription; - private eventCountSubscription?: Subscription; private mounted: boolean = false; private generateId = htmlIdGenerator(); @@ -92,6 +96,7 @@ export class EmbeddablePanel extends React.Component { hidePanelTitles, closeContextMenu: false, badges: [], + notifications: [], }; this.embeddableRoot = React.createRef(); @@ -113,6 +118,22 @@ export class EmbeddablePanel extends React.Component { }); } + private async refreshNotifications() { + let notifications = await this.props.getActions(PANEL_NOTIFICATION_TRIGGER, { + embeddable: this.props.embeddable, + }); + if (!this.mounted) return; + + const { disabledActions } = this.props.embeddable.getInput(); + if (disabledActions) { + notifications = notifications.filter(badge => disabledActions.indexOf(badge.id) === -1); + } + + this.setState({ + notifications, + }); + } + public UNSAFE_componentWillMount() { this.mounted = true; const { embeddable } = this.props; @@ -125,6 +146,7 @@ export class EmbeddablePanel extends React.Component { }); this.refreshBadges(); + this.refreshNotifications(); } }); @@ -136,6 +158,7 @@ export class EmbeddablePanel extends React.Component { }); this.refreshBadges(); + this.refreshNotifications(); } }); } @@ -146,9 +169,6 @@ export class EmbeddablePanel extends React.Component { if (this.subscription) { this.subscription.unsubscribe(); } - if (this.eventCountSubscription) { - this.eventCountSubscription.unsubscribe(); - } if (this.parentSubscription) { this.parentSubscription.unsubscribe(); } @@ -188,9 +208,9 @@ export class EmbeddablePanel extends React.Component { closeContextMenu={this.state.closeContextMenu} title={title} badges={this.state.badges} + notifications={this.state.notifications} embeddable={this.props.embeddable} headerId={headerId} - eventCount={this.state.eventCount} /> )}
@@ -202,17 +222,6 @@ export class EmbeddablePanel extends React.Component { if (this.embeddableRoot.current) { this.props.embeddable.render(this.embeddableRoot.current); } - - const dynamicActions = (this.props.embeddable.enhancements as any)?.dynamicActions; - if (dynamicActions) { - this.setState({ eventCount: dynamicActions.state.get().events.length }); - this.eventCountSubscription = dynamicActions.state.state$.subscribe( - ({ events }: { events: unknown[] }) => { - if (!this.mounted) return; - this.setState({ eventCount: events.length }); - } - ); - } } 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 2a856af7ae9161..144913ecbac6f7 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 @@ -39,9 +39,9 @@ export interface PanelHeaderProps { getActionContextMenuPanel: () => Promise; closeContextMenu: boolean; badges: Array>; + notifications: Array>; embeddable: IEmbeddable; headerId?: string; - eventCount?: number; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -58,6 +58,21 @@ function renderBadges(badges: Array>, embeddable: IEmb )); } +function renderNotifications( + notifications: Array>, + embeddable: IEmbeddable +) { + return notifications.map(notification => ( + notification.execute({ embeddable })} + > + {notification.getDisplayName({ embeddable })} + + )); +} + function renderTooltip(description: string) { return ( description !== '' && ( @@ -90,9 +105,9 @@ export function PanelHeader({ getActionContextMenuPanel, closeContextMenu, badges, + notifications, embeddable, headerId, - eventCount, }: PanelHeaderProps) { const viewDescription = getViewDescription(embeddable); const showTitle = !isViewMode || (title && !hidePanelTitles) || viewDescription !== ''; @@ -150,11 +165,7 @@ export function PanelHeader({ )} {renderBadges(badges, embeddable)} - {!isViewMode && !!eventCount && ( - - {eventCount} - - )} + {renderNotifications(notifications, embeddable)} = { id: PANEL_BADGE_TRIGGER, title: 'Panel badges', - description: 'Actions appear in title bar when an embeddable loads in a panel', + description: 'Actions appear in title bar when an embeddable loads in a panel.', +}; + +export const PANEL_NOTIFICATION_TRIGGER = 'PANEL_NOTIFICATION_TRIGGER'; +export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { + id: PANEL_NOTIFICATION_TRIGGER, + title: 'Panel notifications', + description: 'Actions appear in top-right corner of a panel.', }; diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/index.ts b/x-pack/plugins/embeddable_enhanced/public/actions/index.ts new file mode 100644 index 00000000000000..b47abd48fd2691 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/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 './panel_notifications_action'; diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts new file mode 100644 index 00000000000000..839379387e0943 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { PanelNotificationsAction } from './panel_notifications_action'; +import { EnhancedEmbeddableContext } from '../types'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; + +const createContext = (events: unknown[] = [], isEditMode = false): EnhancedEmbeddableContext => + ({ + embeddable: { + getInput: () => + ({ + viewMode: isEditMode ? ViewMode.EDIT : ViewMode.VIEW, + } as unknown), + enhancements: { + dynamicActions: { + state: { + get: () => + ({ + events, + } as unknown), + }, + }, + }, + }, + } as EnhancedEmbeddableContext); + +describe('PanelNotificationsAction', () => { + describe('getDisplayName', () => { + test('returns "0" if embeddable has no events', async () => { + const context = createContext(); + const action = new PanelNotificationsAction(); + + const name = await action.getDisplayName(context); + expect(name).toBe('0'); + }); + + test('returns "2" if embeddable has two events', async () => { + const context = createContext([{}, {}]); + const action = new PanelNotificationsAction(); + + const name = await action.getDisplayName(context); + expect(name).toBe('2'); + }); + }); + + describe('isCompatible', () => { + test('returns false if not in "edit" mode', async () => { + const context = createContext([{}]); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(false); + }); + + test('returns true when in "edit" mode', async () => { + const context = createContext([{}], true); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(true); + }); + + test('returns false when no embeddable has no events', async () => { + const context = createContext([], true); + const action = new PanelNotificationsAction(); + + const result = await action.isCompatible(context); + expect(result).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts new file mode 100644 index 00000000000000..19e0ac2a5a6d8e --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/actions/panel_notifications_action.ts @@ -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 { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddableContext, EnhancedEmbeddable } from '../types'; + +export const ACTION_PANEL_NOTIFICATIONS = 'ACTION_PANEL_NOTIFICATIONS'; + +/** + * This action renders in "edit" mode number of events (dynamic action) a panel + * has attached to it. + */ +export class PanelNotificationsAction implements ActionDefinition { + public readonly id = ACTION_PANEL_NOTIFICATIONS; + + private getEventCount(embeddable: EnhancedEmbeddable): number { + return embeddable.enhancements.dynamicActions.state.get().events.length; + } + + public readonly getDisplayName = ({ embeddable }: EnhancedEmbeddableContext) => { + return String(this.getEventCount(embeddable)); + }; + + public readonly isCompatible = async ({ embeddable }: EnhancedEmbeddableContext) => { + if (embeddable.getInput().viewMode !== ViewMode.EDIT) return false; + return this.getEventCount(embeddable) > 0; + }; + + public readonly execute = async () => {}; +} diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index a7a56d2c0d78d0..28503e83c9547e 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -16,14 +16,22 @@ import { IEmbeddable, defaultEmbeddableFactoryProvider, EmbeddableContext, + PANEL_NOTIFICATION_TRIGGER, } from '../../../../src/plugins/embeddable/public'; -import { EnhancedEmbeddable } from './types'; +import { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, } from '../../advanced_ui_actions/public'; +import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; + +declare module '../../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [ACTION_PANEL_NOTIFICATIONS]: EnhancedEmbeddableContext; + } +} export interface SetupDependencies { embeddable: EmbeddableSetup; @@ -50,6 +58,10 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); + const panelNotificationAction = new PanelNotificationsAction(); + plugins.advancedUiActions.registerAction(panelNotificationAction); + plugins.advancedUiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); + return {}; } From 96f01d5b0bbb16824cfafc7515fb31d4291a9d52 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 16 Apr 2020 14:36:10 +0200 Subject: [PATCH 088/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20typecheck?= =?UTF-8?q?=20errors=20after=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../embeddable/public/lib/panel/embeddable_panel.test.tsx | 4 ++-- src/plugins/ui_actions/public/actions/action_internal.ts | 4 ++-- .../public/dynamic_actions/action_factory.ts | 7 +------ .../public/dynamic_actions/action_factory_definition.ts | 4 +++- 4 files changed, 8 insertions(+), 11 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 c0934451d9df4e..e2cbd494d5c730 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -224,7 +224,7 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { return undefined; }, }; - const getActions = () => Promise.resolve([action]); + const getActions = () => Promise.resolve([action]) as any; const { component: component1 } = await renderInEditModeAndOpenContextMenu( { @@ -260,7 +260,7 @@ test('HelloWorldContainer hides disabled badges', async () => { return undefined; }, }; - const getActions = () => Promise.resolve([action]); + const getActions = () => Promise.resolve([action]) as any; const { component: component1 } = await renderInEditModeAndOpenContextMenu( { diff --git a/src/plugins/ui_actions/public/actions/action_internal.ts b/src/plugins/ui_actions/public/actions/action_internal.ts index 245ded991c0324..4cbc4dd2a053c7 100644 --- a/src/plugins/ui_actions/public/actions/action_internal.ts +++ b/src/plugins/ui_actions/public/actions/action_internal.ts @@ -51,8 +51,8 @@ export class ActionInternal return await this.definition.isCompatible(context); } - public getHref(context: Context): string | undefined { + public async getHref(context: Context): Promise { if (!this.definition.getHref) return undefined; - return this.definition.getHref(context); + return await this.definition.getHref(context); } } diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts index cf2e16e1805e7e..cebdfe1dd4444d 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts @@ -17,7 +17,7 @@ export class ActionFactory< Config extends object = object, FactoryContext extends object = object, ActionContext extends object = object -> implements Presentable, Configurable { +> implements Omit, 'getHref'>, Configurable { constructor( protected readonly def: ActionFactoryDefinition ) {} @@ -47,11 +47,6 @@ export class ActionFactory< return await this.def.isCompatible(context); } - public getHref(context: FactoryContext): string | undefined { - if (!this.def.getHref) return undefined; - return this.def.getHref(context); - } - public create( serializedAction: Omit, 'factoryId'> ): ActionDefinition { diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts index 7cb21ee29f70ce..fab926f8e0030b 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts @@ -18,7 +18,9 @@ export interface ActionFactoryDefinition< Config extends object = object, FactoryContext extends object = object, ActionContext extends object = object -> extends Partial>, Configurable { +> + extends Partial, 'getHref'>>, + 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 type and From b241b7fcf442aeb1740f11604eac1a7ad469cfcf Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 17 Apr 2020 09:26:03 +0200 Subject: [PATCH 089/129] support getHref in drilldowns (#63727) --- .../plugins/drilldowns/public/services/drilldown_service.ts | 2 ++ x-pack/plugins/drilldowns/public/types.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts index cafaa945683546..8e84bab4bb2918 100644 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts @@ -45,6 +45,7 @@ export class DrilldownService { getDisplayName, euiIcon, execute, + getHref, }: DrilldownDefinition) => { const actionFactory: ActionFactoryDefinition< Config, @@ -64,6 +65,7 @@ export class DrilldownService { getIconType: () => euiIcon, getDisplayName: () => serializedAction.name, execute: async context => await execute(serializedAction.config, context), + getHref: getHref ? async context => getHref(serializedAction.config, context) : undefined, }), } as ActionFactoryDefinition< Config, diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index a8232887f9ca6b..20caecf501a3bd 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -99,6 +99,11 @@ export interface DrilldownDefinition< * `UIAction` of this drilldown is being executed in. */ execute(config: Config, context: ExecutionContext): void; + + /** + * A link where drilldown should navigate on middle click or Ctrl + click. + */ + getHref?(config: Config, context: ExecutionContext): Promise; } /** From 8fcef642855ef93ba145c4c91fdef51d86ce30fc Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 20 Apr 2020 10:58:58 +0200 Subject: [PATCH 090/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20ui=5Fa?= =?UTF-8?q?ctions=20storybook=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/scripts/storybook.js | 26 --------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/plugins/ui_actions/scripts/storybook.js diff --git a/src/plugins/ui_actions/scripts/storybook.js b/src/plugins/ui_actions/scripts/storybook.js deleted file mode 100644 index cb2eda610170d0..00000000000000 --- a/src/plugins/ui_actions/scripts/storybook.js +++ /dev/null @@ -1,26 +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 { join } from 'path'; - -// eslint-disable-next-line -require('@kbn/storybook').runStorybookCli({ - name: 'ui_actions', - storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], -}); From a750efe98d7d58aea1c561cdc1d20c846208eccd Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 22 Apr 2020 11:22:22 +0200 Subject: [PATCH 091/129] update docs --- .../data/public/kibana-plugin-plugins-data-public.search.md | 3 ++- src/plugins/data/public/public.api.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 78ac05b9fd3861..9a22339fd05307 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -27,8 +27,9 @@ search: { InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; + isNumberType: (agg: import("./search").AggConfig) => boolean; isStringType: (agg: import("./search").AggConfig) => boolean; - isType: (type: string) => (agg: import("./search").AggConfig) => boolean; + isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean; isValidEsInterval: typeof isValidEsInterval; isValidInterval: typeof isValidInterval; parentPipelineType: string; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index ff1502f5fdb7b8..db3c0746bd433e 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -6,7 +6,7 @@ import { $Values } from '@kbn/utility-types'; import _ from 'lodash'; -import { Action } from 'history'; +import { Action as Action_2 } from 'history'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { Breadcrumb } from '@elastic/eui'; @@ -54,7 +54,8 @@ import { Subscription } from 'rxjs'; import { Toast } from 'kibana/public'; import { ToastsStart } from 'kibana/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { UiActionsStart as UiActionsStart_2 } from 'src/plugins/ui_actions/public'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; From 1c799d0a777310881184cdf5c9c74ad0c412349a Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 22 Apr 2020 12:56:59 +0200 Subject: [PATCH 092/129] fix ts --- .../drilldowns/dashboard_to_dashboard_drilldown/types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 398a259491e3ef..28200d45b4daf2 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 @@ -5,13 +5,14 @@ */ import { - EmbeddableVisTriggerContext, + RangeSelectTriggerContext, + ValueClickTriggerContext, EmbeddableContext, } from '../../../../../../../src/plugins/embeddable/public'; import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; export type PlaceContext = EmbeddableContext; -export type ActionContext = EmbeddableVisTriggerContext; +export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; export interface Config { dashboardId?: string; From cd4693beb8023b30898da83d032f399b74957c44 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 23 Apr 2020 12:22:54 +0200 Subject: [PATCH 093/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20broken=20me?= =?UTF-8?q?rge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/components/action_wizard/action_wizard.tsx | 6 +++--- 1 file changed, 3 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 ac6aead489edd5..bf8b82abf229c1 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 @@ -12,7 +12,7 @@ import { EuiIcon, EuiSpacer, EuiText, - EuiKeyPadMenuItemButton, + EuiKeyPadMenuItem, } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; @@ -178,7 +178,7 @@ const ActionFactorySelector: React.FC = ({ .sort((f1, f2) => f1.order - f2.order) .map(actionFactory => ( - = ({ {actionFactory.getIconType(context) && ( )} - + ))} From 02524f16db07a8a31c34050626dc807c30d66b4a Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Fri, 24 Apr 2020 09:26:53 +0200 Subject: [PATCH 094/129] [Drilldowns] Dashboard to dashboard drilldown (#63108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * partial progress on async loading / searching of dashboard titles * feat: 🎸 make combobox full width * filtering combobox polish * storybook fix * implement navigating to dashboard, seems like a type problem * try navToApp * filter out current dashboard * rough draft linking to a dashboard * remove note * typefix * fix navigation from dashboard to dashboard except for back button - that would be addressed separatly * partial progress getting filters from action data * fix issue with getIndexPatterns undefined we can’t import those functions as static functions, instead we have to expose them on plugin contract because they are statefull * fix filter / time passing into url * typefix * dashboard to dashboard drilldown functional test and back button fix * documentation update * chore clean-ups fix type * basic unit test for dashboard drilldown * remove test todos decided to skip those tests because not clear how to test due to EuiCombobox is using react-virtualized and options list is not rendered in jsdom env * remove config * improve back button with filter comparison tweak * dashboard filters/date option off by default * revert change to config/kibana.yml * remove unneeded comments * use default time range as appropriate * fix type, add filter icon, add text * fix test * change how time range is restored and improve back button for drilldowns * resolve conflicts * fix async compile issue * remove redundant test * wip * wip * fix * temp skip tests * fix * handle missing dashboard edge case * fix api * refactor action filter creation utils * updating * updating docs * improve * fix storybook * post merge fixes * fix payload emitted in brush event * properly export createRange action * improve tests * add test * post merge fixes * improve * fix * improve * fix build * wip getHref support * implement getHref() * give proper name to a story * use sync start services * update text * fix types * fix ts * fix docs * move clone below drilldowns (near replace) * remove redundant comments * refactor action filter creation utils * updating * updating docs * fix payload emitted in brush event * properly export createRange action * some more updates * fixing types * ... * inline EventData * fix typescript in lens and update docs * improve filters types * docs * merge * @mdefazio review * adjust actions order * docs * @stacey-gammon review Co-authored-by: Matt Kime Co-authored-by: streamich Co-authored-by: ppisljar --- ...na-plugin-plugins-data-public.esfilters.md | 1 + .../actions/clone_panel_action.tsx | 2 +- .../public/actions/apply_filter_action.ts | 1 + src/plugins/data/public/index.ts | 2 + src/plugins/data/public/public.api.md | 98 ++--- .../data/public/query/timefilter/index.ts | 2 +- .../timefilter/lib/change_time_filter.ts | 10 +- src/plugins/embeddable/public/index.ts | 2 + .../lib/panel/panel_header/panel_header.tsx | 1 + .../public/lib/triggers/triggers.ts | 18 +- src/plugins/kibana_utils/public/index.ts | 1 + .../public/context_menu/open_context_menu.tsx | 6 +- .../public/triggers/trigger_internal.ts | 4 +- .../ui_actions/public/util/configurable.ts | 2 +- .../functional/page_objects/dashboard_page.ts | 27 +- .../action_wizard/action_wizard.test.tsx | 6 +- .../action_wizard/action_wizard.tsx | 8 +- x-pack/plugins/dashboard_enhanced/kibana.json | 2 +- .../public/components/README.md | 5 - .../dashboard_enhanced/public/plugin.ts | 5 + .../flyout_create_drilldown.test.tsx | 4 +- .../flyout_create_drilldown.tsx | 9 +- .../flyout_edit_drilldown.test.tsx | 4 +- .../flyout_edit_drilldown.tsx | 9 +- .../flyout_edit_drilldown/menu_item.test.tsx | 2 +- .../flyout_edit_drilldown/menu_item.tsx | 2 +- .../dashboard_drilldowns_services.ts | 27 +- .../collect_config.tsx | 55 --- .../components/collect_config_container.tsx | 171 +++++++++ .../dashboard_drilldown_config.story.tsx | 19 +- .../dashboard_drilldown_config.test.tsx | 9 +- .../dashboard_drilldown_config.tsx | 49 ++- .../dashboard_drilldown_config/i18n.ts | 14 + .../dashboard_drilldown_config/index.ts | 0 .../components/i18n.ts | 16 + .../components/index.ts | 2 +- .../drilldown.test.tsx | 338 +++++++++++++++++- .../drilldown.tsx | 131 ++++++- .../dashboard_to_dashboard_drilldown/types.ts | 12 +- .../dashboard_enhanced/scripts/storybook.js | 2 +- .../connected_flyout_manage_drilldowns.tsx | 2 +- .../i18n.ts | 6 +- .../drilldown_hello_bar.tsx | 2 +- .../flyout_drilldown_wizard.tsx | 1 + .../form_drilldown_wizard.test.tsx | 8 +- .../form_drilldown_wizard.tsx | 2 +- .../list_manage_drilldowns.tsx | 10 +- x-pack/plugins/drilldowns/public/index.ts | 2 +- .../drilldowns/dashboard_drilldowns.ts | 176 +++++++++ .../apps/dashboard/drilldowns/index.ts | 13 + .../test/functional/apps/dashboard/index.ts | 1 + .../dashboard/drilldowns/data.json.gz | Bin 0 -> 2640 bytes .../dashboard/drilldowns/mappings.json | 244 +++++++++++++ .../services/dashboard/drilldowns_manage.ts | 95 +++++ .../functional/services/dashboard/index.ts} | 5 +- .../dashboard/panel_drilldown_actions.ts | 80 +++++ x-pack/test/functional/services/index.ts | 6 + 57 files changed, 1504 insertions(+), 227 deletions(-) delete mode 100644 x-pack/plugins/dashboard_enhanced/public/components/README.md delete 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/components/collect_config_container.tsx rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx (76%) rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx (52%) rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx (52%) rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/dashboard_drilldown_config/i18n.ts (54%) rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/dashboard_drilldown_config/index.ts (100%) create mode 100644 x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/i18n.ts rename x-pack/plugins/dashboard_enhanced/public/{ => services/drilldowns/dashboard_to_dashboard_drilldown}/components/index.ts (77%) create mode 100644 x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts create mode 100644 x-pack/test/functional/apps/dashboard/drilldowns/index.ts create mode 100644 x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz create mode 100644 x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json create mode 100644 x-pack/test/functional/services/dashboard/drilldowns_manage.ts rename x-pack/{plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx => test/functional/services/dashboard/index.ts} (58%) create mode 100644 x-pack/test/functional/services/dashboard/panel_drilldown_actions.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index 7fd65e5db35f36..37142cf1794c32 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -49,6 +49,7 @@ esFilters: { generateFilters: typeof generateFilters; onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean; changeTimeFilter: typeof changeTimeFilter; + convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; } diff --git a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx index 4d15e7e899fa89..ff4e50ba8c3275 100644 --- a/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx @@ -39,7 +39,7 @@ export interface ClonePanelActionContext { export class ClonePanelAction implements ActionByType { public readonly type = ACTION_CLONE_PANEL; public readonly id = ACTION_CLONE_PANEL; - public order = 11; + public order = 45; constructor(private core: CoreStart) {} diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index bd20c6f632a3a5..ebaac6b745bec1 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -42,6 +42,7 @@ export function createFilterAction( return createAction({ type: ACTION_GLOBAL_APPLY_FILTER, id: ACTION_GLOBAL_APPLY_FILTER, + getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('data.filter.applyFilterActionTitle', { defaultMessage: 'Apply filter to current view', diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index e1e2576b2a0e73..cd47719746458c 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -59,6 +59,7 @@ import { changeTimeFilter, mapAndFlattenFilters, extractTimeFilter, + convertRangeFilterToTimeRangeString, } from './query'; // Filter helpers namespace: @@ -96,6 +97,7 @@ export const esFilters = { onlyDisabledFiltersChanged, changeTimeFilter, + convertRangeFilterToTimeRangeString, mapAndFlattenFilters, extractTimeFilter, }; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 59f56d3983b35e..1e94ec70517882 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -392,6 +392,7 @@ export const esFilters: { generateFilters: typeof generateFilters; onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean; changeTimeFilter: typeof changeTimeFilter; + convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; }; @@ -1786,54 +1787,55 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts index a6260e782c12ff..3b7de45799a004 100644 --- a/src/plugins/data/public/query/timefilter/index.ts +++ b/src/plugins/data/public/query/timefilter/index.ts @@ -23,5 +23,5 @@ export * from './types'; export { Timefilter, TimefilterContract } from './timefilter'; export { TimeHistory, TimeHistoryContract } from './time_history'; export { getTime } from './get_time'; -export { changeTimeFilter } from './lib/change_time_filter'; +export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter'; export { extractTimeFilter } from './lib/extract_time_filter'; diff --git a/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts index 8da83580ef5d6b..cbbf2f27543129 100644 --- a/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts @@ -20,7 +20,7 @@ import moment from 'moment'; import { keys } from 'lodash'; import { TimefilterContract } from '../../timefilter'; -import { RangeFilter } from '../../../../common'; +import { RangeFilter, TimeRange } from '../../../../common'; export function convertRangeFilterToTimeRange(filter: RangeFilter) { const key = keys(filter.range)[0]; @@ -32,6 +32,14 @@ export function convertRangeFilterToTimeRange(filter: RangeFilter) { }; } +export function convertRangeFilterToTimeRangeString(filter: RangeFilter): TimeRange { + const { from, to } = convertRangeFilterToTimeRange(filter); + return { + from: from?.toISOString(), + to: to?.toISOString(), + }; +} + export function changeTimeFilter(timeFilter: TimefilterContract, filter: RangeFilter) { timeFilter.setTime(convertRangeFilterToTimeRange(filter)); } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 00761fbb23c780..e61ad2a6eefedd 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -67,6 +67,8 @@ export { withEmbeddableSubscription, SavedObjectEmbeddableInput, isSavedObjectEmbeddableInput, + isRangeSelectTriggerContext, + isValueClickTriggerContext, } from './lib'; export function plugin(initializerContext: PluginInitializerContext) { 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 144913ecbac6f7..35a10ed848e838 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 @@ -64,6 +64,7 @@ function renderNotifications( ) { return notifications.map(notification => ( notification.execute({ embeddable })} diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index fcd9ca6d2186a5..c097e3e8c13be8 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -17,16 +17,16 @@ * under the License. */ -import { Trigger } from '../../../../ui_actions/public'; import { KibanaDatatable } from '../../../../expressions'; +import { Trigger } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; export interface EmbeddableContext { embeddable: IEmbeddable; } -export interface ValueClickTriggerContext { - embeddable?: IEmbeddable; +export interface ValueClickTriggerContext { + embeddable?: T; timeFieldName?: string; data: { data: Array<{ @@ -39,8 +39,12 @@ export interface ValueClickTriggerContext { }; } -export interface RangeSelectTriggerContext { - embeddable?: IEmbeddable; +export const isValueClickTriggerContext = ( + context: ValueClickTriggerContext | RangeSelectTriggerContext +): context is ValueClickTriggerContext => context.data && 'data' in context.data; + +export interface RangeSelectTriggerContext { + embeddable?: T; timeFieldName?: string; data: { table: KibanaDatatable; @@ -49,6 +53,10 @@ export interface RangeSelectTriggerContext { }; } +export const isRangeSelectTriggerContext = ( + context: ValueClickTriggerContext | RangeSelectTriggerContext +): context is RangeSelectTriggerContext => context.data && 'range' in context.data; + export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { id: CONTEXT_MENU_TRIGGER, diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 2f139050e994ab..a1ef075d89d860 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -76,6 +76,7 @@ export { } from './state_sync'; export { removeQueryParam, redirectWhenMissing, ensureDefaultIndexPattern } from './history'; export { applyDiff } from './state_management/utils/diff_object'; +export { createStartServicesGetter } from './core/create_start_service_getter'; /** dummy plugin, we just want kibanaUtils to have its own bundle */ export function plugin() { diff --git a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx index 4d794618e85abe..c723388c021e98 100644 --- a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx +++ b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx @@ -149,7 +149,11 @@ export function openContextMenu( anchorPosition="downRight" withTitle > - + , container ); diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index 14c21ae55dded5..e499c404ae7457 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -68,6 +68,8 @@ export class TriggerInternal { title: this.trigger.title, closeMenu: () => session.close(), }); - const session = openContextMenu([panel]); + const session = openContextMenu([panel], { + 'data-test-subj': 'multipleActionsContextMenu', + }); } } diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/ui_actions/public/util/configurable.ts index d3a527a2183b11..0a5a703abd0c18 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/ui_actions/public/util/configurable.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; /** * Represents something that can be configured by user using UI. diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index b76ce141a44184..36a7674c47ab0c 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -104,16 +104,21 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide public async getDashboardIdFromCurrentUrl() { const currentUrl = await browser.getCurrentUrl(); - const urlSubstring = 'kibana#/dashboard/'; - const startOfIdIndex = currentUrl.indexOf(urlSubstring) + urlSubstring.length; - const endIndex = currentUrl.indexOf('?'); - const id = currentUrl.substring(startOfIdIndex, endIndex < 0 ? currentUrl.length : endIndex); + const id = this.getDashboardIdFromUrl(currentUrl); log.debug(`Dashboard id extracted from ${currentUrl} is ${id}`); return id; } + public getDashboardIdFromUrl(url: string) { + const urlSubstring = 'kibana#/dashboard/'; + const startOfIdIndex = url.indexOf(urlSubstring) + urlSubstring.length; + const endIndex = url.indexOf('?'); + const id = url.substring(startOfIdIndex, endIndex < 0 ? url.length : endIndex); + return id; + } + /** * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). * @returns {Promise} @@ -512,6 +517,20 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide return checkList.filter(viz => viz.isPresent === false).map(viz => viz.name); } + + public async getPanelDrilldownCount(panelIndex = 0): Promise { + log.debug('getPanelDrilldownCount'); + const panel = (await this.getDashboardPanels())[panelIndex]; + try { + const count = await panel.findByTestSubject( + 'embeddablePanelNotification-ACTION_PANEL_NOTIFICATIONS' + ); + return Number.parseInt(await count.getVisibleText(), 10); + } catch (e) { + // if not found then this is 0 (we don't show badge with 0) + return 0; + } + } } return new DashboardPage(); 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 cc56714fcb2f84..f43d832b1edaec 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 @@ -18,7 +18,7 @@ test('Pick and configure action', () => { const screen = render(); // check that all factories are displayed to pick - expect(screen.getAllByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).toHaveLength(2); + expect(screen.getAllByTestId(new RegExp(TEST_SUBJ_ACTION_FACTORY_ITEM))).toHaveLength(2); // select URL one fireEvent.click(screen.getByText(/Go to URL/i)); @@ -43,8 +43,8 @@ test('If only one actions factory is available then actionFactory selection is e const screen = render(); // check that no factories are displayed to pick from - expect(screen.queryByTestId(TEST_SUBJ_ACTION_FACTORY_ITEM)).not.toBeInTheDocument(); - expect(screen.queryByTestId(TEST_SUBJ_SELECTED_ACTION_FACTORY)).toBeInTheDocument(); + expect(screen.queryByTestId(new RegExp(TEST_SUBJ_ACTION_FACTORY_ITEM))).not.toBeInTheDocument(); + expect(screen.queryByTestId(new RegExp(TEST_SUBJ_SELECTED_ACTION_FACTORY))).toBeInTheDocument(); // Input url const URL = 'https://elastic.co'; 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 bf8b82abf229c1..16f01cde784d9f 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 @@ -105,7 +105,7 @@ interface SelectedActionFactoryProps { onDeselect: () => void; } -export const TEST_SUBJ_SELECTED_ACTION_FACTORY = 'selected-action-factory'; +export const TEST_SUBJ_SELECTED_ACTION_FACTORY = 'selectedActionFactory'; const SelectedActionFactory: React.FC = ({ actionFactory, @@ -118,7 +118,7 @@ const SelectedActionFactory: React.FC = ({ return (
@@ -159,7 +159,7 @@ interface ActionFactorySelectorProps { onActionFactorySelected: (actionFactory: ActionFactory) => void; } -export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'action-factory-item'; +export const TEST_SUBJ_ACTION_FACTORY_ITEM = 'actionFactoryItem'; const ActionFactorySelector: React.FC = ({ actionFactories, @@ -181,7 +181,7 @@ const ActionFactorySelector: React.FC = ({ onActionFactorySelected(actionFactory)} > {actionFactory.getIconType(context) && ( diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index 942b01e2b2f3ef..d2ef156c820236 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["uiActions", "embeddable", "dashboard", "drilldowns"], + "requiredPlugins": ["data","uiActions", "embeddable", "dashboard", "drilldowns", "share"], "configPath": ["xpack", "dashboardEnhanced"] } diff --git a/x-pack/plugins/dashboard_enhanced/public/components/README.md b/x-pack/plugins/dashboard_enhanced/public/components/README.md deleted file mode 100644 index 8081f8a2451cfa..00000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/components/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index e0f2aff7c70a82..75d25af2e0132e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -5,21 +5,26 @@ */ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { SharePluginStart, SharePluginSetup } from '../../../../src/plugins/share/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { drilldowns: DrilldownsSetup; embeddable: EmbeddableSetup; uiActions: UiActionsSetup; + share: SharePluginSetup; } export interface StartDependencies { drilldowns: DrilldownsStart; embeddable: EmbeddableStart; uiActions: UiActionsStart; + share: SharePluginStart; + data: DataPublicPluginStart; } // eslint-disable-next-line 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 dcdefba04d882b..83bbad9ae6b21a 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 @@ -20,8 +20,8 @@ const drilldowns = drilldownsPluginMock.createStartContract(); const uiActions = uiActionsPluginMock.createStartContract(); const actionParams: OpenFlyoutAddDrilldownParams = { - drilldowns: () => Promise.resolve(drilldowns), - overlays: () => Promise.resolve(overlays), + drilldowns: () => drilldowns, + overlays: () => overlays, }; test('should create', () => { 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 d901bb8cf4e471..20eaba91786340 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 @@ -16,8 +16,8 @@ import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddabl export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; export interface OpenFlyoutAddDrilldownParams { - overlays: () => Promise; - drilldowns: () => Promise; + overlays: () => CoreStart['overlays']; + drilldowns: () => DrilldownsStart; } export class FlyoutCreateDrilldownAction implements ActionByType { @@ -50,8 +50,8 @@ export class FlyoutCreateDrilldownAction implements ActionByType Promise.resolve(drilldowns), - overlays: () => Promise.resolve(overlays), + drilldowns: () => drilldowns, + overlays: () => overlays, }; test('should create', () => { 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 23a6689bd8d9dc..3a4eb4a4b86446 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 @@ -20,8 +20,8 @@ import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/publ export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; export interface FlyoutEditDrilldownParams { - overlays: () => Promise; - drilldowns: () => Promise; + overlays: () => CoreStart['overlays']; + drilldowns: () => DrilldownsStart; } export class FlyoutEditDrilldownAction implements ActionByType { @@ -48,8 +48,8 @@ export class FlyoutEditDrilldownAction implements ActionByType, + core: CoreSetup, plugins: SetupDependencies, { enableDrilldowns }: BootstrapParams ) { @@ -39,10 +40,17 @@ export class DashboardDrilldownsService { } } - 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; + setupDrilldowns(core: CoreSetup, plugins: SetupDependencies) { + const getStartServices = createStartServicesGetter( + core.getStartServices + ); + + const overlays = () => getStartServices().core.overlays; + const drilldowns = () => getStartServices().plugins.drilldowns; + const getSavedObjectsClient = () => getStartServices().core.savedObjects.client; + const getApplicationService = () => getStartServices().core.application; + const getGetUrlGenerator = () => getStartServices().plugins.share.urlGenerators.getUrlGenerator; + const getDataPluginActions = () => getStartServices().plugins.data.actions; const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); @@ -51,7 +59,10 @@ export class DashboardDrilldownsService { plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ - savedObjects, + getSavedObjectsClient, + getGetUrlGenerator, + getApplicationService, + getDataPluginActions, }); plugins.drilldowns.registerDrilldown(dashboardToDashboardDrilldown); } 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 deleted file mode 100644 index e463cc38b6fbf7..00000000000000 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.tsx +++ /dev/null @@ -1,55 +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, 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, - useCurrentFilters: !config.useCurrentFilters, - }) - } - onKeepRangeToggle={() => - onConfig({ - ...config, - useCurrentDateRange: !config.useCurrentDateRange, - }) - } - /> - ); -}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx new file mode 100644 index 00000000000000..a75f459bbb100c --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -0,0 +1,171 @@ +/* + * 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 { EuiComboBoxOptionOption } from '@elastic/eui'; +import { debounce, findIndex } from 'lodash'; +import { CoreStart, SimpleSavedObject } from '../../../../../../../../src/core/public'; +import { DashboardDrilldownConfig } from './dashboard_drilldown_config'; +import { txtDestinationDashboardNotFound } from './i18n'; +import { UiActionsCollectConfigProps } from '../../../../../../../../src/plugins/ui_actions/public'; +import { DrilldownFactoryContext } from '../../../../../../drilldowns/public'; +import { Config } from '../types'; +import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; + +const mergeDashboards = ( + dashboards: Array>, + selectedDashboard?: EuiComboBoxOptionOption +) => { + // if we have a selected dashboard and its not in the list, append it + if (selectedDashboard && findIndex(dashboards, { value: selectedDashboard.value }) === -1) { + return [selectedDashboard, ...dashboards]; + } + return dashboards; +}; + +const dashboardSavedObjectToMenuItem = ( + savedObject: SimpleSavedObject<{ + title: string; + }> +) => ({ + value: savedObject.id, + label: savedObject.attributes.title, +}); + +interface CollectConfigProps extends UiActionsCollectConfigProps { + deps: { + getSavedObjectsClient: () => CoreStart['savedObjects']['client']; + }; + context: DrilldownFactoryContext<{ + embeddable: IEmbeddable; + }>; +} + +interface CollectConfigContainerState { + dashboards: Array>; + searchString?: string; + isLoading: boolean; + selectedDashboard?: EuiComboBoxOptionOption; + error?: string; +} + +export class CollectConfigContainer extends React.Component< + CollectConfigProps, + CollectConfigContainerState +> { + private isMounted = true; + state = { + dashboards: [], + isLoading: false, + searchString: undefined, + selectedDashboard: undefined, + error: undefined, + }; + + constructor(props: CollectConfigProps) { + super(props); + this.debouncedLoadDashboards = debounce(this.loadDashboards.bind(this), 500); + } + + componentDidMount() { + this.loadSelectedDashboard(); + this.loadDashboards(); + } + + componentWillUnmount() { + this.isMounted = false; + } + + render() { + const { config, onConfig } = this.props; + const { dashboards, selectedDashboard, isLoading, error } = this.state; + + return ( + { + onConfig({ ...config, dashboardId }); + if (this.state.error) { + this.setState({ error: undefined }); + } + }} + onSearchChange={this.debouncedLoadDashboards} + onCurrentFiltersToggle={() => + onConfig({ + ...config, + useCurrentFilters: !config.useCurrentFilters, + }) + } + onKeepRangeToggle={() => + onConfig({ + ...config, + useCurrentDateRange: !config.useCurrentDateRange, + }) + } + /> + ); + } + + private async loadSelectedDashboard() { + const { config } = this.props; + if (!config.dashboardId) return; + const savedObjectsClient = this.props.deps.getSavedObjectsClient(); + const savedObject = await savedObjectsClient.get<{ title: string }>( + 'dashboard', + config.dashboardId + ); + + if (!this.isMounted) return; + + // handle case when destination dashboard is no longer exist + if (savedObject.error?.statusCode === 404) { + this.setState({ + error: txtDestinationDashboardNotFound(config.dashboardId), + }); + this.props.onConfig({ ...config, dashboardId: undefined }); + return; + } + + if (savedObject.error) { + this.setState({ + error: savedObject.error.message, + }); + this.props.onConfig({ ...config, dashboardId: undefined }); + return; + } + + this.setState({ selectedDashboard: dashboardSavedObjectToMenuItem(savedObject) }); + } + + private readonly debouncedLoadDashboards: (searchString?: string) => void; + private async loadDashboards(searchString?: string) { + const currentDashboardId = this.props.context.placeContext.embeddable?.parent?.id; + this.setState({ searchString, isLoading: true }); + const savedObjectsClient = this.props.deps.getSavedObjectsClient(); + const { savedObjects } = await savedObjectsClient.find<{ title: string }>({ + type: 'dashboard', + search: searchString ? `${searchString}*` : undefined, + searchFields: ['title^3', 'description'], + defaultSearchOperator: 'AND', + perPage: 100, + }); + + // bail out if this response is no longer needed + if (!this.isMounted) return; + if (searchString !== this.state.searchString) return; + + const dashboardList = savedObjects + .map(dashboardSavedObjectToMenuItem) + .filter(({ value }) => !currentDashboardId || value !== currentDashboardId); + + this.setState({ dashboards: dashboardList, isLoading: false }); + } +} 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/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx similarity index 76% rename from x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx index 8e204b044a1368..f3a966a73509c2 100644 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.story.tsx @@ -8,12 +8,12 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; -import { DashboardDrilldownConfig } from '.'; +import { DashboardDrilldownConfig } from './dashboard_drilldown_config'; export const dashboards = [ - { id: 'dashboard1', title: 'Dashboard 1' }, - { id: 'dashboard2', title: 'Dashboard 2' }, - { id: 'dashboard3', title: 'Dashboard 3' }, + { value: 'dashboard1', label: 'Dashboard 1' }, + { value: 'dashboard2', label: 'Dashboard 2' }, + { value: 'dashboard3', label: 'Dashboard 3' }, ]; const InteractiveDemo: React.FC = () => { @@ -30,16 +30,23 @@ const InteractiveDemo: React.FC = () => { onDashboardSelect={id => setActiveDashboardId(id)} onCurrentFiltersToggle={() => setCurrentFilters(old => !old)} onKeepRangeToggle={() => setKeepRange(old => !old)} + onSearchChange={() => {}} + isLoading={false} /> ); }; -storiesOf('components/DashboardDrilldownConfig', module) +storiesOf( + 'services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config', + module +) .add('default', () => ( console.log('onDashboardSelect', e)} + onSearchChange={() => {}} + isLoading={false} /> )) .add('with switches', () => ( @@ -49,6 +56,8 @@ storiesOf('components/DashboardDrilldownConfig', module) onDashboardSelect={e => console.log('onDashboardSelect', e)} onCurrentFiltersToggle={() => console.log('onCurrentFiltersToggle')} onKeepRangeToggle={() => console.log('onKeepRangeToggle')} + onSearchChange={() => {}} + isLoading={false} /> )) .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/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx similarity index 52% rename from x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx index 911ff6f6326357..edeb7de48d9acf 100644 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.test.tsx @@ -4,8 +4,7 @@ * 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'); +// Need to wait for https://github.com/elastic/eui/pull/3173/ +// to unit test this component +// basic interaction is covered in end-to-end tests +test.todo(''); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx similarity index 52% rename from x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx index b45ba602b9bb1b..a41a5fb718219d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/dashboard_drilldown_config.tsx @@ -5,22 +5,24 @@ */ import React from 'react'; -import { EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; -import { txtChooseDestinationDashboard } from './i18n'; - -export interface DashboardItem { - id: string; - title: string; -} +import { EuiFormRow, EuiSwitch, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { + txtChooseDestinationDashboard, + txtUseCurrentFilters, + txtUseCurrentDateRange, +} from './i18n'; export interface DashboardDrilldownConfigProps { activeDashboardId?: string; - dashboards: DashboardItem[]; + dashboards: Array>; currentFilters?: boolean; keepRange?: boolean; onDashboardSelect: (dashboardId: string) => void; onCurrentFiltersToggle?: () => void; onKeepRangeToggle?: () => void; + onSearchChange: (searchString: string) => void; + isLoading: boolean; + error?: string; } export const DashboardDrilldownConfig: React.FC = ({ @@ -31,24 +33,35 @@ export const DashboardDrilldownConfig: React.FC = onDashboardSelect, onCurrentFiltersToggle, onKeepRangeToggle, + onSearchChange, + isLoading, + error, }) => { - // TODO: use i18n below. + const selectedTitle = dashboards.find(item => item.value === activeDashboardId)?.label || ''; + return ( <> - - ({ value: id, text: title }))} - value={activeDashboardId} - onChange={e => onDashboardSelect(e.target.value)} + + + async + selectedOptions={ + activeDashboardId ? [{ label: selectedTitle, value: activeDashboardId }] : [] + } + options={dashboards} + onChange={([{ value = '' } = { value: '' }]) => onDashboardSelect(value)} + onSearchChange={onSearchChange} + isLoading={isLoading} + singleSelection={{ asPlainText: true }} + fullWidth + data-test-subj={'dashboardDrilldownSelectDashboard'} + isInvalid={!!error} /> {!!onCurrentFiltersToggle && ( @@ -58,7 +71,7 @@ export const DashboardDrilldownConfig: React.FC = diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts similarity index 54% rename from x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts index 38fe6dd1508536..a37f2bfa01bd49 100644 --- a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/i18n.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/i18n.ts @@ -12,3 +12,17 @@ export const txtChooseDestinationDashboard = i18n.translate( defaultMessage: 'Choose destination dashboard', } ); + +export const txtUseCurrentFilters = i18n.translate( + 'xpack.dashboard.components.DashboardDrilldownConfig.useCurrentFilters', + { + defaultMessage: 'Use filters and query from origin dashboard', + } +); + +export const txtUseCurrentDateRange = i18n.translate( + 'xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange', + { + defaultMessage: 'Use date range from origin dashboard', + } +); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/index.ts similarity index 100% rename from x-pack/plugins/dashboard_enhanced/public/components/dashboard_drilldown_config/index.ts rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/dashboard_drilldown_config/index.ts diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/i18n.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/i18n.ts new file mode 100644 index 00000000000000..6f6f7412f6b538 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/i18n.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. + */ + +import { i18n } from '@kbn/i18n'; + +export const txtDestinationDashboardNotFound = (dashboardId?: string) => + i18n.translate('xpack.dashboard.drilldown.errorDestinationDashboardIsMissing', { + defaultMessage: + "Destination dashboard ('{dashboardId}') no longer exists. Choose another dashboard.", + values: { + dashboardId, + }, + }); diff --git a/x-pack/plugins/dashboard_enhanced/public/components/index.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/index.ts similarity index 77% rename from x-pack/plugins/dashboard_enhanced/public/components/index.ts rename to x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/index.ts index b9a64a3cc17e6e..c34290528d914b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/components/index.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './dashboard_drilldown_config'; +export { CollectConfigContainer } from './collect_config_container'; 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 index 0fb60bb1064a1b..8b69b20cac3e38 100644 --- 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 @@ -4,17 +4,335 @@ * you may not use this file except in compliance with the Elastic License. */ +import { DashboardToDashboardDrilldown } from './drilldown'; +import { UrlGeneratorContract } from '../../../../../../../src/plugins/share/public'; +import { savedObjectsServiceMock } from '../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { ActionContext, Config } from './types'; +import { + Filter, + FilterStateStore, + Query, + RangeFilter, + TimeRange, +} from '../../../../../../../src/plugins/data/common'; +import { esFilters } from '../../../../../../../src/plugins/data/public'; + +// convenient to use real implementation here. +import { createDirectAccessDashboardLinkGenerator } from '../../../../../../../src/plugins/dashboard/public/url_generator'; +import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; +import { + RangeSelectTriggerContext, + ValueClickTriggerContext, +} from '../../../../../../../src/plugins/embeddable/public'; + describe('.isConfigValid()', () => { - test.todo('returns false for incorrect config'); - test.todo('returns true for incorrect config'); + const drilldown = new DashboardToDashboardDrilldown({} as any); + + test('returns false for invalid config with missing dashboard id', () => { + expect( + drilldown.isConfigValid({ + dashboardId: '', + useCurrentDateRange: false, + useCurrentFilters: false, + }) + ).toBe(false); + }); + + test('returns true for valid config', () => { + expect( + drilldown.isConfigValid({ + dashboardId: 'id', + useCurrentDateRange: false, + useCurrentFilters: false, + }) + ).toBe(true); + }); +}); + +test('config component exist', () => { + const drilldown = new DashboardToDashboardDrilldown({} as any); + expect(drilldown.CollectConfig).toEqual(expect.any(Function)); +}); + +test('initial config: switches are ON', () => { + const drilldown = new DashboardToDashboardDrilldown({} as any); + const { useCurrentDateRange, useCurrentFilters } = drilldown.createConfig(); + expect(useCurrentDateRange).toBe(true); + expect(useCurrentFilters).toBe(true); }); -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' - ); +test('getHref is defined', () => { + const drilldown = new DashboardToDashboardDrilldown({} as any); + expect(drilldown.getHref).toBeDefined(); }); + +describe('.execute() & getHref', () => { + /** + * A convenience test setup helper + * Beware: `dataPluginMock.createStartContract().actions` and extracting filters from event is mocked! + * The url generation is not mocked and uses real implementation + * So this tests are mostly focused on making sure the filters returned from `dataPluginMock.createStartContract().actions` helpers + * end up in resulting navigation path + */ + async function setupTestBed( + config: Partial, + embeddableInput: { filters?: Filter[]; timeRange?: TimeRange; query?: Query }, + filtersFromEvent: Filter[], + useRangeEvent = false + ) { + const navigateToApp = jest.fn(); + const getUrlForApp = jest.fn((app, opt) => `${app}/${opt.path}`); + const dataPluginActions = dataPluginMock.createStartContract().actions; + const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; + + const drilldown = new DashboardToDashboardDrilldown({ + getApplicationService: () => ({ + navigateToApp, + getUrlForApp, + }), + getGetUrlGenerator: () => () => + createDirectAccessDashboardLinkGenerator(() => + Promise.resolve({ appBasePath: 'test', useHashedUrl: false }) + ) as UrlGeneratorContract, + getDataPluginActions: () => dataPluginActions, + getSavedObjectsClient: () => savedObjectsClient, + }); + const selectRangeFiltersSpy = jest + .spyOn(dataPluginActions, 'createFiltersFromRangeSelectAction') + .mockImplementation(() => Promise.resolve(filtersFromEvent)); + const valueClickFiltersSpy = jest + .spyOn(dataPluginActions, 'createFiltersFromValueClickAction') + .mockImplementation(() => Promise.resolve(filtersFromEvent)); + + const completeConfig: Config = { + dashboardId: 'id', + useCurrentFilters: false, + useCurrentDateRange: false, + ...config, + }; + + const context = ({ + data: useRangeEvent + ? ({ range: {} } as RangeSelectTriggerContext['data']) + : ({ data: [] } as ValueClickTriggerContext['data']), + timeFieldName: 'order_date', + embeddable: { + getInput: () => ({ + filters: [], + timeRange: { from: 'now-15m', to: 'now' }, + query: { query: 'test', language: 'kuery' }, + ...embeddableInput, + }), + }, + } as unknown) as ActionContext; + + await drilldown.execute(completeConfig, context); + + if (useRangeEvent) { + expect(selectRangeFiltersSpy).toBeCalledTimes(1); + expect(valueClickFiltersSpy).toBeCalledTimes(0); + } else { + expect(selectRangeFiltersSpy).toBeCalledTimes(0); + expect(valueClickFiltersSpy).toBeCalledTimes(1); + } + + expect(navigateToApp).toBeCalledTimes(1); + expect(navigateToApp.mock.calls[0][0]).toBe('kibana'); + + const executeNavigatedPath = navigateToApp.mock.calls[0][1]?.path; + const href = await drilldown.getHref(completeConfig, context); + + expect(href.includes(executeNavigatedPath)).toBe(true); + + return { + href, + }; + } + + test('navigates to correct dashboard', async () => { + const testDashboardId = 'dashboardId'; + const { href } = await setupTestBed( + { + dashboardId: testDashboardId, + }, + {}, + [], + false + ); + + expect(href).toEqual(expect.stringContaining(`dashboard/${testDashboardId}`)); + }); + + test('query is removed if filters are disabled', async () => { + const queryString = 'querystring'; + const queryLanguage = 'kuery'; + const { href } = await setupTestBed( + { + useCurrentFilters: false, + }, + { + query: { query: queryString, language: queryLanguage }, + }, + [] + ); + + expect(href).toEqual(expect.not.stringContaining(queryString)); + expect(href).toEqual(expect.not.stringContaining(queryLanguage)); + }); + + test('navigates with query if filters are enabled', async () => { + const queryString = 'querystring'; + const queryLanguage = 'kuery'; + const { href } = await setupTestBed( + { + useCurrentFilters: true, + }, + { + query: { query: queryString, language: queryLanguage }, + }, + [] + ); + + expect(href).toEqual(expect.stringContaining(queryString)); + expect(href).toEqual(expect.stringContaining(queryLanguage)); + }); + + test('when user chooses to keep current filters, current filters are set on destination dashboard', async () => { + const existingAppFilterKey = 'appExistingFilter'; + const existingGlobalFilterKey = 'existingGlobalFilter'; + const newAppliedFilterKey = 'newAppliedFilter'; + + const { href } = await setupTestBed( + { + useCurrentFilters: true, + }, + { + filters: [getFilter(false, existingAppFilterKey), getFilter(true, existingGlobalFilterKey)], + }, + [getFilter(false, newAppliedFilterKey)] + ); + + expect(href).toEqual(expect.stringContaining(existingAppFilterKey)); + expect(href).toEqual(expect.stringContaining(existingGlobalFilterKey)); + expect(href).toEqual(expect.stringContaining(newAppliedFilterKey)); + }); + + test('when user chooses to remove current filters, current app filters are remove on destination dashboard', async () => { + const existingAppFilterKey = 'appExistingFilter'; + const existingGlobalFilterKey = 'existingGlobalFilter'; + const newAppliedFilterKey = 'newAppliedFilter'; + + const { href } = await setupTestBed( + { + useCurrentFilters: false, + }, + { + filters: [getFilter(false, existingAppFilterKey), getFilter(true, existingGlobalFilterKey)], + }, + [getFilter(false, newAppliedFilterKey)] + ); + + expect(href).not.toEqual(expect.stringContaining(existingAppFilterKey)); + expect(href).toEqual(expect.stringContaining(existingGlobalFilterKey)); + expect(href).toEqual(expect.stringContaining(newAppliedFilterKey)); + }); + + test('when user chooses to keep current time range, current time range is passed in url', async () => { + const { href } = await setupTestBed( + { + useCurrentDateRange: true, + }, + { + timeRange: { + from: 'now-300m', + to: 'now', + }, + }, + [] + ); + + expect(href).toEqual(expect.stringContaining('now-300m')); + }); + + test('when user chooses to not keep current time range, no current time range is passed in url', async () => { + const { href } = await setupTestBed( + { + useCurrentDateRange: false, + }, + { + timeRange: { + from: 'now-300m', + to: 'now', + }, + }, + [], + false + ); + + expect(href).not.toEqual(expect.stringContaining('now-300m')); + }); + + test('if range filter contains date, then it is passed as time', async () => { + const { href } = await setupTestBed( + { + useCurrentDateRange: true, + }, + { + timeRange: { + from: 'now-300m', + to: 'now', + }, + }, + [getMockTimeRangeFilter()], + true + ); + + expect(href).not.toEqual(expect.stringContaining('now-300m')); + expect(href).toEqual(expect.stringContaining('2020-03-23')); + }); +}); + +function getFilter(isPinned: boolean, queryKey: string): Filter { + return { + $state: { + store: isPinned ? esFilters.FilterStateStore.GLOBAL_STATE : FilterStateStore.APP_STATE, + }, + meta: { + index: 'logstash-*', + disabled: false, + negate: false, + alias: null, + }, + query: { + match: { + [queryKey]: 'any', + }, + }, + }; +} + +function getMockTimeRangeFilter(): RangeFilter { + return { + meta: { + index: 'logstash-*', + params: { + gte: '2020-03-23T13:10:29.665Z', + lt: '2020-03-23T13:10:36.736Z', + format: 'strict_date_optional_time', + }, + type: 'range', + key: 'order_date', + disabled: false, + negate: false, + alias: null, + }, + range: { + order_date: { + gte: '2020-03-23T13:10:29.665Z', + lt: '2020-03-23T13:10:36.736Z', + format: 'strict_date_optional_time', + }, + }, + }; +} 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 9d2a378f08acd7..68aac6c2bd360e 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,19 +7,32 @@ import React from 'react'; import { CoreStart } from 'src/core/public'; import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; -import { CollectConfigContainer } from './collect_config'; +import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; +import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../src/plugins/dashboard/public'; +import { PlaceContext, ActionContext, Config } from './types'; +import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { DrilldownDefinition as Drilldown } from '../../../../../drilldowns/public'; import { txtGoToDashboard } from './i18n'; +import { DataPublicPluginStart, esFilters } from '../../../../../../../src/plugins/data/public'; +import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; +import { + isRangeSelectTriggerContext, + isValueClickTriggerContext, +} from '../../../../../../../src/plugins/embeddable/public'; export interface Params { - savedObjects: () => Promise; + getSavedObjectsClient: () => CoreStart['savedObjects']['client']; + getApplicationService: () => Pick; + getGetUrlGenerator: () => SharePluginStart['urlGenerators']['getUrlGenerator']; + getDataPluginActions: () => DataPublicPluginStart['actions']; } export class DashboardToDashboardDrilldown - implements Drilldown { - constructor(protected readonly params: Params) {} + implements Drilldown> { + constructor(protected readonly params: Params) { + this.getDestinationUrl = this.getDestinationUrl.bind(this); + } public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -29,14 +42,14 @@ export class DashboardToDashboardDrilldown public readonly euiIcon = 'dashboardApp'; - private readonly ReactCollectConfig: React.FC = props => ( - + private readonly ReactCollectConfig: React.FC = props => ( + ); public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); public readonly createConfig = () => ({ - dashboardId: '123', + dashboardId: '', useCurrentFilters: true, useCurrentDateRange: true, }); @@ -46,7 +59,105 @@ export class DashboardToDashboardDrilldown return true; }; - public readonly execute = () => { - alert('Go to another dashboard!'); + public readonly getHref = async ( + config: Config, + context: ActionContext + ): Promise => { + const { getUrlForApp } = await this.params.getApplicationService(); + const dashboardPath = await this.getDestinationUrl(config, context); + // note: extracting hash and using 'kibana' as appId will be redundant, + // when dashboard move to np urls. (urlGenerator generates np url, which is not supported yet) + const dashboardHash = dashboardPath.split('#')[1]; + return getUrlForApp('kibana', { + path: `#${dashboardHash}`, + }); }; + + public readonly execute = async ( + config: Config, + context: ActionContext + ) => { + const { navigateToApp } = await this.params.getApplicationService(); + const dashboardPath = await this.getDestinationUrl(config, context); + // note: extracting hash and using 'kibana' as appId will be redundant, + // when dashboard move to np urls. (urlGenerator generates np url, which is not supported yet) + const dashboardHash = dashboardPath.split('#')[1]; + await navigateToApp('kibana', { + path: `#${dashboardHash}`, + }); + }; + + private async getDestinationUrl( + config: Config, + context: ActionContext + ): Promise { + const getUrlGenerator = await this.params.getGetUrlGenerator(); + + const { + createFiltersFromRangeSelectAction, + createFiltersFromValueClickAction, + } = await this.params.getDataPluginActions(); + const { + timeRange: currentTimeRange, + query, + filters: currentFilters, + } = context.embeddable!.getInput(); + + // if useCurrentDashboardFilters enabled, then preserve all the filters (pinned and unpinned) + // otherwise preserve only pinned + const existingFilters = + (config.useCurrentFilters + ? currentFilters + : currentFilters?.filter(f => esFilters.isFilterPinned(f))) ?? []; + + // if useCurrentDashboardDataRange is enabled, then preserve current time range + // if undefined is passed, then destination dashboard will figure out time range itself + // for brush event this time range would be overwritten + let timeRange = config.useCurrentDateRange ? currentTimeRange : undefined; + let filtersFromEvent = await (async () => { + try { + if (isRangeSelectTriggerContext(context)) + return await createFiltersFromRangeSelectAction(context.data); + if (isValueClickTriggerContext(context)) + return await createFiltersFromValueClickAction(context.data); + + // eslint-disable-next-line no-console + console.warn( + ` + DashboardToDashboard drilldown: can't extract filters from action. + Is it not supported action?`, + context + ); + + return []; + } catch (e) { + // eslint-disable-next-line no-console + console.warn( + ` + DashboardToDashboard drilldown: error extracting filters from action. + Continuing without applying filters from event`, + e + ); + return []; + } + })(); + + if (context.timeFieldName) { + const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( + context.timeFieldName, + filtersFromEvent + ); + filtersFromEvent = restOfFilters; + if (timeRangeFilter) { + timeRange = esFilters.convertRangeFilterToTimeRangeString(timeRangeFilter); + } + } + + return getUrlGenerator(DASHBOARD_APP_URL_GENERATOR).createUrl({ + dashboardId: config.dashboardId, + query: config.useCurrentFilters ? query : undefined, + timeRange, + filters: [...existingFilters, ...filtersFromEvent], + }); + } } 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 28200d45b4daf2..530d48e216530a 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 @@ -5,19 +5,19 @@ */ import { - RangeSelectTriggerContext, - ValueClickTriggerContext, EmbeddableContext, + ValueClickTriggerContext, + RangeSelectTriggerContext, + IEmbeddable, } from '../../../../../../../src/plugins/embeddable/public'; -import { UiActionsCollectConfigProps } from '../../../../../../../src/plugins/ui_actions/public'; export type PlaceContext = EmbeddableContext; -export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; +export type ActionContext = + | ValueClickTriggerContext + | RangeSelectTriggerContext; export interface Config { dashboardId?: string; useCurrentFilters: boolean; useCurrentDateRange: 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 index f2cbe4135f4cb6..5d95c56c31e3b1 100644 --- a/x-pack/plugins/dashboard_enhanced/scripts/storybook.js +++ b/x-pack/plugins/dashboard_enhanced/scripts/storybook.js @@ -9,5 +9,5 @@ import { join } from 'path'; // eslint-disable-next-line require('@kbn/storybook').runStorybookCli({ name: 'dashboard_enhanced', - storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.story.tsx')], + storyGlobs: [join(__dirname, '..', 'public', '**', '*.story.tsx')], }); 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 c8a67a20aa5c16..f049b231c15cae 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 @@ -22,7 +22,7 @@ import { SELECT_RANGE_TRIGGER, TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; -import { useContainerState } from '../../../../../../src/plugins/kibana_utils/common'; +import { useContainerState } from '../../../../../../src/plugins/kibana_utils/public'; import { DrilldownListItem } from '../list_manage_drilldowns'; import { toastDrilldownCreated, 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 index 70f4d735e2a749..31384860786efe 100644 --- 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 @@ -15,7 +15,7 @@ export const toastDrilldownCreated = { ), text: (drilldownName: string) => i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownCreatedText', { - defaultMessage: 'You created "{drilldownName}"', + defaultMessage: 'You created "{drilldownName}". Save dashboard before testing.', values: { drilldownName, }, @@ -31,7 +31,7 @@ export const toastDrilldownEdited = { ), text: (drilldownName: string) => i18n.translate('xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownEditedText', { - defaultMessage: 'You edited "{drilldownName}"', + defaultMessage: 'You edited "{drilldownName}". Save dashboard before testing.', values: { drilldownName, }, @@ -48,7 +48,7 @@ export const toastDrilldownDeleted = { text: i18n.translate( 'xpack.drilldowns.components.flyoutDrilldownWizard.toast.drilldownDeletedText', { - defaultMessage: 'You deleted a drilldown', + defaultMessage: 'You deleted a drilldown.', } ), }; 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 8c6739a8ad6c8a..48e17dadc810f0 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 @@ -23,7 +23,7 @@ export interface DrilldownHelloBarProps { onHideClick?: () => void; } -export const WELCOME_MESSAGE_TEST_SUBJ = 'drilldowns-welcome-message-test-subj'; +export const WELCOME_MESSAGE_TEST_SUBJ = 'drilldownsWelcomeMessage'; export const DrilldownHelloBar: React.FC = ({ docsLink, 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 faa965a98a4bb4..8541aae06ff0c7 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 @@ -79,6 +79,7 @@ export function FlyoutDrilldownWizard {mode === 'edit' ? txtEditDrilldownButtonLabel : txtCreateDrilldownButtonLabel} 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 4560773cc8a6dd..d9c53ae6f737a4 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 @@ -24,9 +24,7 @@ describe('', () => { render(, div); - const input = div.querySelector( - '[data-test-subj="dynamicActionNameInput"]' - ) as HTMLInputElement; + const input = div.querySelector('[data-test-subj="drilldownNameInput"]') as HTMLInputElement; expect(input?.value).toBe(''); }); @@ -36,9 +34,7 @@ describe('', () => { render(, div); - const input = div.querySelector( - '[data-test-subj="dynamicActionNameInput"]' - ) as HTMLInputElement; + const input = div.querySelector('[data-test-subj="drilldownNameInput"]') as HTMLInputElement; expect(input?.value).toBe('foo'); 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 bdafaaf07873c5..93b3710bf6cc66 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 @@ -46,7 +46,7 @@ export const FormDrilldownWizard: React.FC = ({ value={name} disabled={onNameChange === noopFn} onChange={event => onNameChange(event.target.value)} - data-test-subj="dynamicActionNameInput" + data-test-subj="drilldownNameInput" /> ); 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 5a15781a1faf2a..ab51c0a829ed35 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 @@ -40,7 +40,7 @@ export interface ListManageDrilldownsProps { const noop = () => {}; -export const TEST_SUBJ_DRILLDOWN_ITEM = 'list-manage-drilldowns-item'; +export const TEST_SUBJ_DRILLDOWN_ITEM = 'listManageDrilldownsItem'; export function ListManageDrilldowns({ drilldowns, @@ -56,6 +56,7 @@ export function ListManageDrilldowns({ name: 'Name', truncateText: true, width: '50%', + 'data-test-subj': 'drilldownListItemName', }, { name: 'Action', @@ -107,7 +108,12 @@ export function ListManageDrilldowns({ {txtCreateDrilldown} ) : ( - onDelete(selectedDrilldowns)}> + onDelete(selectedDrilldowns)} + data-test-subj={'listManageDeleteDrilldowns'} + > {txtDeleteDrilldowns(selectedDrilldowns.length)} )} diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 044e29c671de40..17ccd60e17ce4d 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 { DrilldownDefinition } from './types'; +export { DrilldownDefinition, DrilldownFactoryContext } from './types'; diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts new file mode 100644 index 00000000000000..1a90d5d1fe52a7 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts @@ -0,0 +1,176 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const DASHBOARD_WITH_PIE_CHART_NAME = 'Dashboard with Pie Chart'; +const DASHBOARD_WITH_AREA_CHART_NAME = 'Dashboard With Area Chart'; + +const DRILLDOWN_TO_PIE_CHART_NAME = 'Go to pie chart dashboard'; +const DRILLDOWN_TO_AREA_CHART_NAME = 'Go to area chart dashboard'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const dashboardPanelActions = getService('dashboardPanelActions'); + const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); + const dashboardDrilldownsManage = getService('dashboardDrilldownsManage'); + const PageObjects = getPageObjects(['dashboard', 'common', 'header', 'timePicker']); + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const pieChart = getService('pieChart'); + const log = getService('log'); + const browser = getService('browser'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const filterBar = getService('filterBar'); + + describe('Dashboard Drilldowns', function() { + before(async () => { + log.debug('Dashboard Drilldowns:initTests'); + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('dashboard/drilldowns'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + }); + + after(async () => { + await esArchiver.unload('dashboard/drilldowns'); + }); + + it('should create dashboard to dashboard drilldown, use it, and then delete it', async () => { + await PageObjects.dashboard.gotoDashboardEditMode(DASHBOARD_WITH_PIE_CHART_NAME); + + // create drilldown + await dashboardPanelActions.openContextMenu(); + await dashboardDrilldownPanelActions.expectExistsCreateDrilldownAction(); + await dashboardDrilldownPanelActions.clickCreateDrilldown(); + await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutOpen(); + await dashboardDrilldownsManage.fillInDashboardToDashboardDrilldownWizard({ + drilldownName: DRILLDOWN_TO_AREA_CHART_NAME, + destinationDashboardTitle: DASHBOARD_WITH_AREA_CHART_NAME, + }); + await dashboardDrilldownsManage.saveChanges(); + await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutClose(); + + // check that drilldown notification badge is shown + expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(1); + + // save dashboard, navigate to view mode + await PageObjects.dashboard.saveDashboard(DASHBOARD_WITH_PIE_CHART_NAME, { + saveAsNew: false, + waitDialogIsClosed: true, + }); + + // trigger drilldown action by clicking on a pie and picking drilldown action by it's name + await pieChart.filterOnPieSlice('40,000'); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + + const href = await dashboardDrilldownPanelActions.getActionHrefByText( + DRILLDOWN_TO_AREA_CHART_NAME + ); + expect(typeof href).to.be('string'); // checking that action has a href + const dashboardIdFromHref = PageObjects.dashboard.getDashboardIdFromUrl(href); + + await navigateWithinDashboard(async () => { + await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_AREA_CHART_NAME); + }); + // checking that href is at least pointing to the same dashboard that we are navigated to by regular click + expect(dashboardIdFromHref).to.be(await PageObjects.dashboard.getDashboardIdFromCurrentUrl()); + + // check that we drilled-down with filter from pie chart + expect(await filterBar.getFilterCount()).to.be(1); + + const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + + // brush area chart and drilldown back to pie chat dashboard + await brushAreaChart(); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + + await navigateWithinDashboard(async () => { + await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME); + }); + + // because filters are preserved during navigation, we expect that only one slice is displayed (filter is still applied) + expect(await filterBar.getFilterCount()).to.be(1); + await pieChart.expectPieSliceCount(1); + + // check that new time range duration was applied + const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); + + // delete drilldown + await PageObjects.dashboard.switchToEditMode(); + await dashboardPanelActions.openContextMenu(); + await dashboardDrilldownPanelActions.expectExistsManageDrilldownsAction(); + await dashboardDrilldownPanelActions.clickManageDrilldowns(); + await dashboardDrilldownsManage.expectsManageDrilldownsFlyoutOpen(); + + await dashboardDrilldownsManage.deleteDrilldownsByTitles([DRILLDOWN_TO_AREA_CHART_NAME]); + await dashboardDrilldownsManage.closeFlyout(); + + // check that drilldown notification badge is shown + expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(0); + }); + + it('browser back/forward navigation works after drilldown navigation', async () => { + await PageObjects.dashboard.loadSavedDashboard(DASHBOARD_WITH_AREA_CHART_NAME); + const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + await brushAreaChart(); + await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); + await navigateWithinDashboard(async () => { + await dashboardDrilldownPanelActions.clickActionByText(DRILLDOWN_TO_PIE_CHART_NAME); + }); + // check that new time range duration was applied + const newTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); + expect(newTimeRangeDurationHours).to.be.lessThan(originalTimeRangeDurationHours); + + await navigateWithinDashboard(async () => { + await browser.goBack(); + }); + + expect(await PageObjects.timePicker.getTimeDurationInHours()).to.be( + originalTimeRangeDurationHours + ); + }); + }); + + // utils which shouldn't be a part of test flow, but also too specific to be moved to pageobject or service + async function brushAreaChart() { + const areaChart = await testSubjects.find('visualizationLoader'); + expect(await areaChart.getAttribute('data-title')).to.be('Visualization漢字 AreaChart'); + await browser.dragAndDrop( + { + location: areaChart, + offset: { + x: -100, + y: 0, + }, + }, + { + location: areaChart, + offset: { + x: 100, + y: 0, + }, + } + ); + } + + async function navigateWithinDashboard(navigationTrigger: () => Promise) { + // before executing action which would trigger navigation: remember current dashboard id in url + const oldDashboardId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); + // execute navigation action + await navigationTrigger(); + // wait until dashboard navigates to a new dashboard with area chart + await retry.waitFor('navigate to different dashboard', async () => { + const newDashboardId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); + return typeof newDashboardId === 'string' && oldDashboardId !== newDashboardId; + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + } +} diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts new file mode 100644 index 00000000000000..ab273018dc3f78 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts @@ -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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('drilldowns', function() { + this.tags(['skipFirefox']); + loadTestFile(require.resolve('./dashboard_drilldowns')); + }); +} diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index 794bd2f1f0db36..d6ce16167d1744 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./preserve_url')); + loadTestFile(require.resolve('./drilldowns')); }); } diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz b/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..febf312799eee0de78a6de2226c17d4fe6183c68 GIT binary patch literal 2640 zcmV-W3a|AaiwFoL|8-sf17u-zVJ>QOZ*BnXT-|cpxD~$7Q?R=1L^3JKlBKISwl{U_ z*d9rn%*OI?AQBYOkOY?iEju1PM|<7-zCfo>)0y@;dH{kHDanqr>jM^`AYmPU%k42y0|xlr6?d@kYI24p?vkPzy9Mt|NPs(|NfWeG2&=~W&!?o zedJtJ#wi3{BR{6LbspqB+2~_*gWZeSpRvQ=V~#4_2&OPf9DgiX`{VfHT=aH7&19H1 zhSNqs=ZqufH-A5Eyq-3GPH}Pv4fpD&&3Hnhd5I#Q%5H|?gS#)-UXZ|bP~t)pPg1;G zLYqedjuW6*pd{Z1cfZqL5!12G#7VRCNwdhE-q2R2?|DZA@1=|z_4loix7t$ z^1~oF!#6l6+J%PDd@io9ggIO}qE%(Up=rb=i8($FktBu~4C1V@1sY%V(ysOb5y$Bq z?p*I_mO7s?-w@CvILJDh-EquhJBh%S5JEt1o2HN(-*OB-uc0M?@o)r| z$Y@3Iey1pHe!!r82L*3Ya^!<0G=}K|ti0@KH6D|Chv8cF^Rh=X;0phAcuSZPSt!GG zCzJ-9#1+~Zo`YMjNDSo4x(`SM#*ux8)XivM3J4)LERrtgXzIP9o6-6t{#6MVl>4kw?fSsbHnO zuPASWRH)a=ug_@`3ZbwlpH{;xashB8${M!>GHH^l*(ZaoR;OjkTLN^K z#qP9`5j?>@mjn_*owXMWuM?(8Pz2?EBC)4XCI3^Va)Cf!DEJC z{4T)RK-0!@mvB1A)Q7;b=LvDY9ZccrmP2Xc5-^9V#sr97CfFfLp-LHXiPZj1=qLfB z8H3(&5)D_N@$HaU>8Xl&QhFwO5w;O8gHja<_Hd$HtI%$aORo!a4P{v_L9^oKa==P7 zX0TPhN8}wTcU~z-yA~XPKPqEA^Z1HV8D707R5cI?Ksln3C=^Eupq80hL#m z?3fa@EV1)?1vH}>XymMt;;KGQlY6vWaMqrXHdbe9!4->X#B{UqCd7>CFBOsA>jRHgO^-ij-lGbKwL%}ZwbeX}UOYlCXKQC>`LYhGTdU$g=r zHCW(=fYmZP!mTb(lF^`*2>z+SO{y zkFM3%?5$AuXDag3G>;_PzD=voC!e*0zS_ca{w^D*vw$#P_Y$$a{;9`~XNB$O`9Am8 zaKIdz;9rk4-^Rjqqq6ZN#6O5n&Ck_#;}!lkw(UhUDs3?cZe+(j~#V_c^Yy z@RGM7Z4-5e`;cOi#mF;lXC&;HMk!x~r2bvVU*7^#TE;8wCG47Y+K8;q3OFN?_TXIS z;RQ#hIw)YP#5)X9u{HQPqg0$6kZmd~JVU>seFrNh!FiQHc}5<&uT@cJd~YX(h)Yy*DcfIJ31n^UQbd=dSL{gyOwr5dI( z24$-xy}c_*kMB$7=7!BK9uj38&&1P@l4w{yQQ4?8Rw~0Gl ztJfZ&_5k<39gT|qABCE7Ncn_|?e^v~fzCR%NZm`#2Pm4S+38u+#^Rm zGU5Gm5|x6~CoaB35e_CI{#WxpWnp8{UUYP|b~Hu5)mKMTi1QlM85(epsNvdOF~n3I zwjp3uynx=R|4CLa_Bwrc-!q%2k9*B-ue0Agu&~?g47~QtLdfZP-Arc5$7zAjPkT^! zYB5 z(V39>JahA_Y2(A;*^ejo(b?(IpX|x#^xeCYk-Tesa&~ewu}6pJ?@m??5|PwbLc{Vj z-#cn>YQzK^V&f+_3g1+-d@BKp7R7hh!u|fhdNej0+ztSkMe`3?+yf@Az>1hqo7pqhot=VXrQZN2h0J#}^;Z#i=3Jh22KjJ1=&v34#qy zQ=MK+u$N%w=Kv&PM>rpF1V1HvD||555@v{hNN6bhvu#@YO%oI_m{|Q`r#rOFR(sI@ zv_6puB^?M;bX{$n&~jJp&crl_=3r>`Th@WO)|JZD$GJI-kA(X&O&c3|x|E7HW<;~o zIwA)PWVtGyso+P3hfgWS)C2j!kV@s%#_d(u3Qg3n>y1g<6<`)kX)JP-Ya^a%u5CiV zNF!Lo{lRd*)9Rbnr(Zt7yUGDi@$=tufE?<7B?lZh`>xlUwVS5n44U18gMJgW!MHs% yK-Qq`b-HGMYYuqYgAe3@|3_?E2yD89?6M1JT3>llch(z|Ui}y8AnvUGR{#K}ArO`T literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json new file mode 100644 index 00000000000000..210fade40c648f --- /dev/null +++ b/x-pack/test/functional/es_archives/dashboard/drilldowns/mappings.json @@ -0,0 +1,244 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/services/dashboard/drilldowns_manage.ts b/x-pack/test/functional/services/dashboard/drilldowns_manage.ts new file mode 100644 index 00000000000000..1710cb8bfb71a6 --- /dev/null +++ b/x-pack/test/functional/services/dashboard/drilldowns_manage.ts @@ -0,0 +1,95 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +const CREATE_DRILLDOWN_FLYOUT_DATA_TEST_SUBJ = 'createDrilldownFlyout'; +const MANAGE_DRILLDOWNS_FLYOUT_DATA_TEST_SUBJ = 'editDrilldownFlyout'; +const DASHBOARD_TO_DASHBOARD_ACTION_LIST_ITEM = + 'actionFactoryItem-DASHBOARD_TO_DASHBOARD_DRILLDOWN'; +const DASHBOARD_TO_DASHBOARD_ACTION_WIZARD = + 'selectedActionFactory-DASHBOARD_TO_DASHBOARD_DRILLDOWN'; +const DESTINATION_DASHBOARD_SELECT = 'dashboardDrilldownSelectDashboard'; +const DRILLDOWN_WIZARD_SUBMIT = 'drilldownWizardSubmit'; + +export function DashboardDrilldownsManageProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const flyout = getService('flyout'); + const comboBox = getService('comboBox'); + + return new (class DashboardDrilldownsManage { + async expectsCreateDrilldownFlyoutOpen() { + log.debug('expectsCreateDrilldownFlyoutOpen'); + await testSubjects.existOrFail(CREATE_DRILLDOWN_FLYOUT_DATA_TEST_SUBJ); + } + + async expectsManageDrilldownsFlyoutOpen() { + log.debug('expectsManageDrilldownsFlyoutOpen'); + await testSubjects.existOrFail(MANAGE_DRILLDOWNS_FLYOUT_DATA_TEST_SUBJ); + } + + async expectsCreateDrilldownFlyoutClose() { + log.debug('expectsCreateDrilldownFlyoutClose'); + await testSubjects.missingOrFail(CREATE_DRILLDOWN_FLYOUT_DATA_TEST_SUBJ); + } + + async expectsManageDrilldownsFlyoutClose() { + log.debug('expectsManageDrilldownsFlyoutClose'); + await testSubjects.missingOrFail(MANAGE_DRILLDOWNS_FLYOUT_DATA_TEST_SUBJ); + } + + async fillInDashboardToDashboardDrilldownWizard({ + drilldownName, + destinationDashboardTitle, + }: { + drilldownName: string; + destinationDashboardTitle: string; + }) { + await this.fillInDrilldownName(drilldownName); + await this.selectDashboardToDashboardActionIfNeeded(); + await this.selectDestinationDashboard(destinationDashboardTitle); + } + + async fillInDrilldownName(name: string) { + await testSubjects.setValue('drilldownNameInput', name); + } + + async selectDashboardToDashboardActionIfNeeded() { + if (await testSubjects.exists(DASHBOARD_TO_DASHBOARD_ACTION_LIST_ITEM)) { + await testSubjects.click(DASHBOARD_TO_DASHBOARD_ACTION_LIST_ITEM); + } + await testSubjects.existOrFail(DASHBOARD_TO_DASHBOARD_ACTION_WIZARD); + } + + async selectDestinationDashboard(title: string) { + await comboBox.set(DESTINATION_DASHBOARD_SELECT, title); + } + + async saveChanges() { + await testSubjects.click(DRILLDOWN_WIZARD_SUBMIT); + } + + async deleteDrilldownsByTitles(titles: string[]) { + const drilldowns = await testSubjects.findAll('listManageDrilldownsItem'); + + for (const drilldown of drilldowns) { + const nameColumn = await drilldown.findByTestSubject('drilldownListItemName'); + const name = await nameColumn.getVisibleText(); + if (titles.includes(name)) { + const checkbox = await drilldown.findByTagName('input'); + await checkbox.click(); + } + } + const deleteBtn = await testSubjects.find('listManageDeleteDrilldowns'); + await deleteBtn.click(); + } + + async closeFlyout() { + await flyout.ensureAllClosed(); + } + })(); +} diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx b/x-pack/test/functional/services/dashboard/index.ts similarity index 58% rename from x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx rename to x-pack/test/functional/services/dashboard/index.ts index 95101605ce4682..dee525fa0a388b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/collect_config.test.tsx +++ b/x-pack/test/functional/services/dashboard/index.ts @@ -4,6 +4,5 @@ * 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'); +export { DashboardDrilldownPanelActionsProvider } from './panel_drilldown_actions'; +export { DashboardDrilldownsManageProvider } from './drilldowns_manage'; diff --git a/x-pack/test/functional/services/dashboard/panel_drilldown_actions.ts b/x-pack/test/functional/services/dashboard/panel_drilldown_actions.ts new file mode 100644 index 00000000000000..febcbdcc9273e3 --- /dev/null +++ b/x-pack/test/functional/services/dashboard/panel_drilldown_actions.ts @@ -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 { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; + +const CREATE_DRILLDOWN_DATA_TEST_SUBJ = 'embeddablePanelAction-OPEN_FLYOUT_ADD_DRILLDOWN'; +const MANAGE_DRILLDOWNS_DATA_TEST_SUBJ = 'embeddablePanelAction-OPEN_FLYOUT_EDIT_DRILLDOWN'; + +export function DashboardDrilldownPanelActionsProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + + return new (class DashboardDrilldownPanelActions { + async expectExistsCreateDrilldownAction() { + log.debug('expectExistsCreateDrilldownAction'); + await testSubjects.existOrFail(CREATE_DRILLDOWN_DATA_TEST_SUBJ); + } + + async expectMissingCreateDrilldwonAction() { + log.debug('expectMissingCreateDrilldownAction'); + await testSubjects.existOrFail(MANAGE_DRILLDOWNS_DATA_TEST_SUBJ); + } + + async clickCreateDrilldown() { + log.debug('clickCreateDrilldown'); + await this.expectExistsCreateDrilldownAction(); + await testSubjects.clickWhenNotDisabled(CREATE_DRILLDOWN_DATA_TEST_SUBJ); + } + + async expectExistsManageDrilldownsAction() { + log.debug('expectExistsCreateDrilldownAction'); + await testSubjects.existOrFail(CREATE_DRILLDOWN_DATA_TEST_SUBJ); + } + + async expectMissingManageDrilldownsAction() { + log.debug('expectExistsRemovePanelAction'); + await testSubjects.existOrFail(MANAGE_DRILLDOWNS_DATA_TEST_SUBJ); + } + + async clickManageDrilldowns() { + log.debug('clickManageDrilldowns'); + await this.expectExistsManageDrilldownsAction(); + await testSubjects.clickWhenNotDisabled(MANAGE_DRILLDOWNS_DATA_TEST_SUBJ); + } + + async expectMultipleActionsMenuOpened() { + log.debug('exceptMultipleActionsMenuOpened'); + await testSubjects.existOrFail('multipleActionsContextMenu'); + } + + async clickActionByText(text: string) { + log.debug(`clickActionByText: "${text}"`); + (await this.getActionWebElementByText(text)).click(); + } + + async getActionHrefByText(text: string) { + log.debug(`getActionHref: "${text}"`); + const item = await this.getActionWebElementByText(text); + return item.getAttribute('href'); + } + + async getActionWebElementByText(text: string): Promise { + log.debug(`getActionWebElement: "${text}"`); + const menu = await testSubjects.find('multipleActionsContextMenu'); + const items = await menu.findAllByCssSelector('[data-test-subj*="embeddablePanelAction-"]'); + for (const item of items) { + const currentText = await item.getVisibleText(); + if (currentText === text) { + return item; + } + } + + throw new Error(`No action matching text "${text}"`); + } + })(); +} diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index aec91ba9e90341..f1d84f3054aa08 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -49,6 +49,10 @@ import { InfraSourceConfigurationFormProvider } from './infra_source_configurati import { LogsUiProvider } from './logs_ui'; import { MachineLearningProvider } from './ml'; import { TransformProvider } from './transform'; +import { + DashboardDrilldownPanelActionsProvider, + DashboardDrilldownsManageProvider, +} from './dashboard'; // define the name and providers for services that should be // available to your tests. If you don't specify anything here @@ -91,4 +95,6 @@ export const services = { logsUi: LogsUiProvider, ml: MachineLearningProvider, transform: TransformProvider, + dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, + dashboardDrilldownsManage: DashboardDrilldownsManageProvider, }; From 4c3cc8d7776bc4688103bb192299bf0efaa2a1e8 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Apr 2020 13:45:08 +0200 Subject: [PATCH 095/129] fix docs --- src/plugins/data/public/public.api.md | 101 +++++++++++++------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 86560b3ccf7b12..b39d2e632db9cc 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -6,7 +6,7 @@ import { $Values } from '@kbn/utility-types'; import _ from 'lodash'; -import { Action } from 'history'; +import { Action as Action_2 } from 'history'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { Breadcrumb } from '@elastic/eui'; @@ -55,7 +55,8 @@ import { Subscription } from 'rxjs'; import { Toast } from 'kibana/public'; import { ToastsStart } from 'kibana/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { UiActionsStart as UiActionsStart_2 } from 'src/plugins/ui_actions/public'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; @@ -384,6 +385,7 @@ export const esFilters: { generateFilters: typeof generateFilters; onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean; changeTimeFilter: typeof changeTimeFilter; + convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; extractTimeFilter: typeof extractTimeFilter; }; @@ -1833,53 +1835,54 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts From 731cec802a0f4ea990577317c2234421a1c853a4 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 28 Apr 2020 14:00:36 +0200 Subject: [PATCH 096/129] nit fixes --- .../embeddable/public/lib/panel/embeddable_panel.test.tsx | 8 ++++---- .../public/components/action_wizard/action_wizard.tsx | 2 +- 2 files changed, 5 insertions(+), 5 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 a6c9402dafbccf..9dd4c74c624d9b 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -223,10 +223,10 @@ test('HelloWorldContainer in edit mode hides disabledActions', async () => { execute: async () => {}, order: 10, getHref: () => { - return undefined; + return Promise.resolve(undefined); }, }; - const getActions = () => Promise.resolve([action]) as any; + const getActions = () => Promise.resolve([action]); const { component: component1 } = await renderInEditModeAndOpenContextMenu( { @@ -259,10 +259,10 @@ test('HelloWorldContainer hides disabled badges', async () => { execute: async () => {}, order: 10, getHref: () => { - return undefined; + return Promise.resolve(undefined); }, }; - const getActions = () => Promise.resolve([action]) as any; + const getActions = () => Promise.resolve([action]); const { component: component1 } = await renderInEditModeAndOpenContextMenu( { 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 16f01cde784d9f..502f7a50c854f8 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 @@ -29,7 +29,7 @@ export interface ActionWizardProps { /** * Currently selected action factory - * undefined - is allowed and means that non is selected + * undefined - is allowed and means that none is selected */ currentActionFactory?: ActionFactory; From 7cffd349284aedd32004e4c55585eb312ca8eef1 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 28 Apr 2020 14:45:01 +0200 Subject: [PATCH 097/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20uiActi?= =?UTF-8?q?ons=20from=20Embeddable=20dependencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/lib/embeddables/embeddable.tsx | 13 ++----------- .../embeddable/create_vis_embeddable_from_object.ts | 2 -- .../public/embeddable/visualize_embeddable.ts | 7 ++----- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 3c5374d6c124c6..9c544e86e189ab 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -16,22 +16,18 @@ * specific language governing permissions and limitations * under the License. */ + import { cloneDeep, isEqual } from 'lodash'; import * as Rx from 'rxjs'; import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { TriggerContextMapping } from '../ui_actions'; -import { UiActionsStart } from '../../../../../plugins/ui_actions/public'; 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 @@ -58,12 +54,7 @@ export abstract class Embeddable< // TODO: Rename to destroyed. private destoyed: boolean = false; - constructor( - input: TEmbeddableInput, - output: TEmbeddableOutput, - parent?: IContainer, - public readonly params: EmbeddableParams = {} - ) { + constructor(input: TEmbeddableInput, output: TEmbeddableOutput, parent?: IContainer) { this.id = input.id; this.output = { title: getPanelTitle(input, output), diff --git a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts index 74c655e517b1f1..8e51bd4ac5d4fa 100644 --- a/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts +++ b/src/plugins/visualizations/public/embeddable/create_vis_embeddable_from_object.ts @@ -27,7 +27,6 @@ import { getHttp, getTimeFilter, getCapabilities, - getUiActions, } from '../services'; import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; @@ -60,7 +59,6 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe indexPatterns, editUrl, editable, - uiActions: getUiActions(), deps, }, input, diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index e7adcb51ced23c..71b31b7f74168f 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -40,7 +40,6 @@ import { IExpressionLoaderParams, ExpressionsStart } from '../../../../plugins/e import { buildPipeline } from '../legacy/build_pipeline'; import { Vis } from '../vis'; import { getExpressions, getUiActions } from '../services'; -import { VisualizationsStartDeps } from '../plugin'; import { VIS_EVENT_TO_TRIGGER } from './events'; import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory'; @@ -51,7 +50,6 @@ export interface VisualizeEmbeddableConfiguration { indexPatterns?: IIndexPattern[]; editUrl: string; editable: boolean; - uiActions?: VisualizationsStartDeps['uiActions']; deps: VisualizeEmbeddableFactoryDeps; } @@ -91,7 +89,7 @@ export class VisualizeEmbeddable extends Embeddable Date: Tue, 28 Apr 2020 15:17:30 +0200 Subject: [PATCH 098/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20don't=20export?= =?UTF-8?q?=20ActionInternal=20from=20ui=5Factions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/index.ts | 1 - .../public/dynamic_actions/dynamic_action_manager.test.ts | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 19c9b0a41a0935..13a6bb505ef136 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -29,7 +29,6 @@ export { UiActionsServiceParams, UiActionsService } from './service'; export { Action, ActionDefinition as UiActionsActionDefinition, - ActionInternal as UiActionsActionInternal, createAction, IncompatibleActionError, } from './actions'; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts index 9e9712304af3f6..b7f1b36f8f3586 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts @@ -6,10 +6,8 @@ import { DynamicActionManager } from './dynamic_action_manager'; import { ActionStorage, MemoryActionStorage } from './dynamic_action_storage'; -import { - UiActionsService, - UiActionsActionInternal as ActionInternal, -} from '../../../../../src/plugins/ui_actions/public'; +import { UiActionsService } from '../../../../../src/plugins/ui_actions/public'; +import { ActionInternal } from '../../../../../src/plugins/ui_actions/public/actions'; import { of } from '../../../../../src/plugins/kibana_utils'; import { UiActionsServiceEnhancements } from '../services'; import { ActionFactoryDefinition } from './action_factory_definition'; From b5d7c8dfe604de52a0966a2b50ad685838d4f41c Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 28 Apr 2020 15:18:40 +0200 Subject: [PATCH 099/129] =?UTF-8?q?test:=20=F0=9F=92=8D=20remove=20uiActio?= =?UTF-8?q?ns=20deps=20in=20x-pack=20test=20mocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flyout_create_drilldown.test.tsx | 5 +---- .../flyout_edit_drilldown/flyout_edit_drilldown.test.tsx | 6 ++---- .../public/services/drilldowns/actions/test_helpers.ts | 9 +++------ 3 files changed, 6 insertions(+), 14 deletions(-) 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 83bbad9ae6b21a..004853febea0e1 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 @@ -11,13 +11,11 @@ import { 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 { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); const actionParams: OpenFlyoutAddDrilldownParams = { drilldowns: () => drilldowns, @@ -63,7 +61,6 @@ describe('isCompatible', () => { supportedTriggers: (isValueClickTriggerSupported ? ['VALUE_CLICK_TRIGGER'] : []) as Array< keyof TriggerContextMapping >, - uiActions, } ); @@ -119,7 +116,7 @@ describe('execute', () => { test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); - const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })); + const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, {})); await drilldownAction.execute({ embeddable, 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 index d85518ee6b8be3..9423beabae7024 100644 --- 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 @@ -64,9 +64,7 @@ describe('isCompatible', () => { id: '', viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW, }; - const embeddable = new MockEmbeddable(input, { - uiActions, - }); + const embeddable = new MockEmbeddable(input, {}); const context = { embeddable: (isEmbeddableEnhanced ? enhanceEmbeddable(embeddable, uiActions) @@ -138,7 +136,7 @@ describe('execute', () => { test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); await drilldownAction.execute({ - embeddable: enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })), + embeddable: enhanceEmbeddable(new MockEmbeddable({ id: '' }, {})), }); expect(spy).toBeCalled(); }); 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 index 47685f43888766..03000b4fc04f3e 100644 --- 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 @@ -11,10 +11,7 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsStart, } from '../../../../../advanced_ui_actions/public'; -import { - TriggerContextMapping, - UiActionsStart, -} from '../../../../../../../src/plugins/ui_actions/public'; +import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actions/public'; import { uiActionsEnhancedPluginMock } from '../../../../../advanced_ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { @@ -22,9 +19,9 @@ export class MockEmbeddable extends Embeddable { private readonly triggers: Array = []; constructor( initialInput: EmbeddableInput, - params: { uiActions?: UiActionsStart; supportedTriggers?: Array } + params: { supportedTriggers?: Array } ) { - super(initialInput, {}, undefined, params); + super(initialInput, {}, undefined); this.triggers = params.supportedTriggers ?? []; } public render(node: HTMLElement) {} From 56c7a27d43d95b7afa3d37076adaec1007409b2c Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 28 Apr 2020 15:31:27 +0200 Subject: [PATCH 100/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20cleanup=20ui=5F?= =?UTF-8?q?actions=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui_actions/public/service/ui_actions_service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 f00877f812b24a..771601953945ec 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -23,9 +23,8 @@ import { TriggerToActionsRegistry, TriggerId, TriggerContextMapping, - ActionType, } from '../types'; -import { ActionInternal, Action, ActionByType, ActionDefinition, ActionContext } from '../actions'; +import { ActionInternal, Action, ActionDefinition, ActionContext } from '../actions'; import { Trigger, TriggerContext } from '../triggers/trigger'; import { TriggerInternal } from '../triggers/trigger_internal'; import { TriggerContract } from '../triggers/trigger_contract'; @@ -134,11 +133,11 @@ export class UiActionsService { ); }; - public readonly addTriggerAction = ( + 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 + action: Action ): void => { if (!this.actions.has(action.id)) this.registerAction(action); this.attachAction(triggerId, action.id); From 937e88aab05ea1161317116d11885b30d975db3a Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 28 Apr 2020 15:42:49 +0200 Subject: [PATCH 101/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20JSDo?= =?UTF-8?q?c=20comment=20to=20addTriggerAction()?= 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 | 6 ++++++ 1 file changed, 6 insertions(+) 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 771601953945ec..8d63fdcec90073 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -133,6 +133,12 @@ export class UiActionsService { ); }; + /** + * `addTriggerAction` is similar to `attachAction` as it attaches action to a + * trigger, but it also registers the action, if it has not been registered, yet. + * + * `addTriggerAction` also infers better typing of the `action` argument. + */ public readonly addTriggerAction = ( triggerId: TType, // The action can accept partial or no context, but if it needs context not provided From ad1b02ab4738cc77528c8c68ea9113fc30bd6727 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 28 Apr 2020 18:07:04 +0200 Subject: [PATCH 102/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20regenerate?= =?UTF-8?q?=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/data/public/public.api.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index b39d2e632db9cc..6c431e413a9e39 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -6,7 +6,7 @@ import { $Values } from '@kbn/utility-types'; import _ from 'lodash'; -import { Action as Action_2 } from 'history'; +import { Action } from 'history'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { Breadcrumb } from '@elastic/eui'; @@ -55,8 +55,7 @@ import { Subscription } from 'rxjs'; import { Toast } from 'kibana/public'; import { ToastsStart } from 'kibana/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; -import { UiActionsStart as UiActionsStart_2 } from 'src/plugins/ui_actions/public'; -import { UiComponent } from 'src/plugins/kibana_utils/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; From 6beb38297bda6655828151f89ba174f8c32e10c9 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 28 Apr 2020 20:11:11 +0200 Subject: [PATCH 103/129] Drilldown demo 2 (#64300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 add example of Discover drilldown to sample plugin * fix: 🐛 show drilldowns with higher "order" first * feat: 🎸 add createStartServicesGetter() to /public kibana_util * feat: 🎸 load index patterns in Discover drilldown * feat: 🎸 add toggle for index pattern selection * feat: 🎸 add spacer to separate unrelated config fields * fix: 🐛 correctly configre setup core * feat: 🎸 navigate to correct index pattern * chore: 🤖 fix type check errors * fix: 🐛 make index pattern select full width * fix: 🐛 add getHref support * feat: 🎸 add example plugin ability to X-Pack * refactor: 💡 move Discover drilldown example to X-Pack * feat: 🎸 add dashboard-to-url drilldown example * feat: 🎸 add new tab support for URL drilldown * feat: 🎸 add "hello world" drilldown example * docs: ✏️ add README * feat: 🎸 add getHref support * chore: 🤖 cleanup after moving examples to X-Pack * docs: ✏️ add to README.md info on how to find drilldowns --- .i18nrc.json | 1 + examples/ui_action_examples/public/index.ts | 3 +- examples/ui_action_examples/public/plugin.ts | 21 +++-- .../index_patterns/index_patterns.ts | 8 +- x-pack/.i18nrc.json | 1 + .../ui_actions_enhanced_examples/README.md | 37 +++++++- .../ui_actions_enhanced_examples/kibana.json | 2 +- .../dashboard_hello_world_drilldown/README.md | 1 + .../dashboard_hello_world_drilldown/index.tsx | 62 +++++++++++++ .../collect_config_container.tsx | 71 ++++++++++++++ .../discover_drilldown_config.tsx | 92 +++++++++++++++++++ .../discover_drilldown_config/i18n.ts | 14 +++ .../discover_drilldown_config/index.ts | 7 ++ .../components/index.ts | 7 ++ .../constants.ts | 7 ++ .../drilldown.tsx | 83 +++++++++++++++++ .../dashboard_to_discover_drilldown/i18n.ts | 11 +++ .../dashboard_to_discover_drilldown/index.ts | 16 ++++ .../dashboard_to_discover_drilldown/types.ts | 40 ++++++++ .../dashboard_to_url_drilldown/index.tsx | 89 ++++++++++++++++++ .../public/plugin.ts | 16 +++- .../action_wizard/action_wizard.tsx | 2 +- .../public/services/drilldown_service.ts | 2 + x-pack/plugins/drilldowns/public/types.ts | 6 ++ 24 files changed, 582 insertions(+), 17 deletions(-) create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/README.md create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/collect_config_container.tsx create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/i18n.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/index.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/index.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/constants.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/i18n.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts create mode 100644 x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx diff --git a/.i18nrc.json b/.i18nrc.json index b04c02f6b22655..be3c043b6e52f7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -8,6 +8,7 @@ "data": "src/plugins/data", "embeddableApi": "src/plugins/embeddable", "embeddableExamples": "examples/embeddable_examples", + "uiActionsExamples": "examples/ui_action_examples", "share": "src/plugins/share", "home": "src/plugins/home", "charts": "src/plugins/charts", diff --git a/examples/ui_action_examples/public/index.ts b/examples/ui_action_examples/public/index.ts index 88a36d278e256e..5b08192a1196ec 100644 --- a/examples/ui_action_examples/public/index.ts +++ b/examples/ui_action_examples/public/index.ts @@ -18,9 +18,8 @@ */ import { UiActionExamplesPlugin } from './plugin'; -import { PluginInitializer } from '../../../src/core/public'; -export const plugin: PluginInitializer = () => new UiActionExamplesPlugin(); +export const plugin = () => new UiActionExamplesPlugin(); export { HELLO_WORLD_TRIGGER_ID } from './hello_world_trigger'; export { ACTION_HELLO_WORLD } from './hello_world_action'; diff --git a/examples/ui_action_examples/public/plugin.ts b/examples/ui_action_examples/public/plugin.ts index d053f7e82862c2..3a9f673261e330 100644 --- a/examples/ui_action_examples/public/plugin.ts +++ b/examples/ui_action_examples/public/plugin.ts @@ -17,15 +17,19 @@ * under the License. */ -import { Plugin, CoreSetup } from '../../../src/core/public'; -import { UiActionsSetup } from '../../../src/plugins/ui_actions/public'; +import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; +import { UiActionsSetup, UiActionsStart } from '../../../src/plugins/ui_actions/public'; import { createHelloWorldAction, ACTION_HELLO_WORLD } from './hello_world_action'; import { helloWorldTrigger, HELLO_WORLD_TRIGGER_ID } from './hello_world_trigger'; -interface UiActionExamplesSetupDependencies { +export interface UiActionExamplesSetupDependencies { uiActions: UiActionsSetup; } +export interface UiActionExamplesStartDependencies { + uiActions: UiActionsStart; +} + declare module '../../../src/plugins/ui_actions/public' { export interface TriggerContextMapping { [HELLO_WORLD_TRIGGER_ID]: {}; @@ -37,8 +41,12 @@ declare module '../../../src/plugins/ui_actions/public' { } export class UiActionExamplesPlugin - implements Plugin { - public setup(core: CoreSetup, { uiActions }: UiActionExamplesSetupDependencies) { + implements + Plugin { + public setup( + core: CoreSetup, + { uiActions }: UiActionExamplesSetupDependencies + ) { uiActions.registerTrigger(helloWorldTrigger); const helloWorldAction = createHelloWorldAction(async () => ({ @@ -49,6 +57,7 @@ export class UiActionExamplesPlugin uiActions.addTriggerAction(helloWorldTrigger.id, helloWorldAction); } - public start() {} + public start(core: CoreStart, plugins: UiActionExamplesStartDependencies) {} + public stop() {} } diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index b5d66a6aab60a0..73d5aeaf30710f 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -37,10 +37,14 @@ const indexPatternCache = createIndexPatternCache(); type IndexPatternCachedFieldType = 'id' | 'title'; +export interface IndexPatternSavedObjectAttrs { + title: string; +} + export class IndexPatternsService { private config: IUiSettingsClient; private savedObjectsClient: SavedObjectsClientContract; - private savedObjectsCache?: Array>> | null; + private savedObjectsCache?: Array> | null; private apiClient: IndexPatternsApiClient; ensureDefaultIndexPattern: EnsureDefaultIndexPattern; @@ -53,7 +57,7 @@ export class IndexPatternsService { private async refreshSavedObjectsCache() { this.savedObjectsCache = ( - await this.savedObjectsClient.find>({ + await this.savedObjectsClient.find({ type: 'index-pattern', fields: ['title'], perPage: 10000, diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 9fddec0c8552f8..11dc7c8fbf1dc6 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -3,6 +3,7 @@ "paths": { "xpack.actions": "plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", + "xpack.uiActionsEnhanced": "examples/ui_actions_enhanced_examples", "xpack.alerting": "plugins/alerting", "xpack.alertingBuiltins": "plugins/alerting_builtins", "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], diff --git a/x-pack/examples/ui_actions_enhanced_examples/README.md b/x-pack/examples/ui_actions_enhanced_examples/README.md index c9f53137d86875..ec049bbd33dec4 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/README.md +++ b/x-pack/examples/ui_actions_enhanced_examples/README.md @@ -1,3 +1,36 @@ -## Ui actions enhanced examples +# Ui actions enhanced examples -To run this example, use the command `yarn start --run-examples`. +To run this example plugin, use the command `yarn start --run-examples`. + + +## Drilldown examples + +This plugin holds few examples on how to add drilldown types to dashboard. + +To play with drilldowns, open any dashboard, click "Edit" to put it in *edit mode*. +Now when opening context menu of dashboard panels you should see "Create drilldown" option. + +![image](https://user-images.githubusercontent.com/9773803/80460907-c2ef7880-8934-11ea-8400-533bb9d57e36.png) + +Once you click "Create drilldown" you should be able to see drilldowns added by +this sample plugin. + +![image](https://user-images.githubusercontent.com/9773803/80460408-131a0b00-8934-11ea-81e4-137e9e33f34b.png) + + +### `dashboard_hello_world_drilldown` + +`dashboard_hello_world_drilldown` is the most basic "hello world" example showing +how a drilldown can be built, all in one file. + +### `dashboard_to_url_drilldown` + +`dashboard_to_url_drilldown` is a good starting point for build a drilldown +that navigates somewhere externally. + +One can see how middle-click or Ctrl + click behavior could be supported using +`getHref` field. + +### `dashboard_to_discover_drilldown` + +`dashboard_to_discover_drilldown` shows how a real-world drilldown could look like. diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json index f75852edced5c0..622f45b016bb3c 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json @@ -5,6 +5,6 @@ "configPath": ["ui_actions_enhanced_examples"], "server": false, "ui": true, - "requiredPlugins": ["uiActions", "data"], + "requiredPlugins": ["uiActions", "drilldowns", "data"], "optionalPlugins": [] } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/README.md b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/README.md new file mode 100644 index 00000000000000..47a3429b16d7a5 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/README.md @@ -0,0 +1 @@ +This folder contains a one-file example of the most basic drilldown implementation. diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx new file mode 100644 index 00000000000000..0ef8528a55ec66 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx @@ -0,0 +1,62 @@ +/* + * 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, EuiFieldText } from '@elastic/eui'; +import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; +import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { + EmbeddableContext, + RangeSelectTriggerContext, + ValueClickTriggerContext, +} from '../../../../../src/plugins/embeddable/public'; +import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; + +export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; + +export interface Config { + name: string; +} + +const SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN = 'SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN'; + +export class DashboardHelloWorldDrilldown + implements Drilldown { + public readonly id = SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN; + + public readonly order = 6; + + public readonly getDisplayName = () => 'Say hello drilldown'; + + public readonly euiIcon = 'cheer'; + + private readonly ReactCollectConfig: React.FC> = ({ + config, + onConfig, + }) => ( + + onConfig({ ...config, name: event.target.value })} + /> + + ); + + public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); + + public readonly createConfig = () => ({ + name: '', + }); + + public readonly isConfigValid = (config: Config): config is Config => { + return !!config.name; + }; + + public readonly execute = async (config: Config, context: ActionContext) => { + alert(`Hello, ${config.name}`); + }; +} diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/collect_config_container.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/collect_config_container.tsx new file mode 100644 index 00000000000000..69cf260a20a81d --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/collect_config_container.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, { useState, useEffect } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { CollectConfigProps } from './types'; +import { DiscoverDrilldownConfig, IndexPatternItem } from './components/discover_drilldown_config'; +import { Params } from './drilldown'; + +export interface CollectConfigContainerProps extends CollectConfigProps { + params: Params; +} + +export const CollectConfigContainer: React.FC = ({ + config, + onConfig, + params: { start }, +}) => { + const isMounted = useMountedState(); + const [indexPatterns, setIndexPatterns] = useState([]); + + useEffect(() => { + (async () => { + const indexPatternSavedObjects = await start().plugins.data.indexPatterns.getCache(); + if (!isMounted()) return; + setIndexPatterns( + indexPatternSavedObjects + ? indexPatternSavedObjects.map(indexPattern => ({ + id: indexPattern.id, + title: indexPattern.attributes.title, + })) + : [] + ); + })(); + }, [isMounted, start]); + + return ( + { + onConfig({ ...config, indexPatternId }); + }} + customIndexPattern={config.customIndexPattern} + onCustomIndexPatternToggle={() => + onConfig({ + ...config, + customIndexPattern: !config.customIndexPattern, + indexPatternId: undefined, + }) + } + carryFiltersAndQuery={config.carryFiltersAndQuery} + onCarryFiltersAndQueryToggle={() => + onConfig({ + ...config, + carryFiltersAndQuery: !config.carryFiltersAndQuery, + }) + } + carryTimeRange={config.carryTimeRange} + onCarryTimeRangeToggle={() => + onConfig({ + ...config, + carryTimeRange: !config.carryTimeRange, + }) + } + /> + ); +}; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx new file mode 100644 index 00000000000000..be54d8b2982e17 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx @@ -0,0 +1,92 @@ +/* + * 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, EuiSpacer } from '@elastic/eui'; +import { txtChooseDestinationIndexPattern } from './i18n'; + +export interface IndexPatternItem { + id: string; + title: string; +} + +export interface DiscoverDrilldownConfigProps { + activeIndexPatternId?: string; + indexPatterns: IndexPatternItem[]; + onIndexPatternSelect: (indexPatternId: string) => void; + customIndexPattern?: boolean; + onCustomIndexPatternToggle?: () => void; + carryFiltersAndQuery?: boolean; + onCarryFiltersAndQueryToggle?: () => void; + carryTimeRange?: boolean; + onCarryTimeRangeToggle?: () => void; +} + +export const DiscoverDrilldownConfig: React.FC = ({ + activeIndexPatternId, + indexPatterns, + onIndexPatternSelect, + customIndexPattern, + onCustomIndexPatternToggle, + carryFiltersAndQuery, + onCarryFiltersAndQueryToggle, + carryTimeRange, + onCarryTimeRangeToggle, +}) => { + return ( + <> + {!!onCustomIndexPatternToggle && ( + <> + + + + {!!customIndexPattern && ( + + ({ value: id, text: title })), + ]} + value={activeIndexPatternId || ''} + onChange={e => onIndexPatternSelect(e.target.value)} + /> + + )} + + + )} + + {!!onCarryFiltersAndQueryToggle && ( + + + + )} + {!!onCarryTimeRangeToggle && ( + + + + )} + + ); +}; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/i18n.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/i18n.ts new file mode 100644 index 00000000000000..ccd75e7dcc3e3d --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_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 txtChooseDestinationIndexPattern = i18n.translate( + 'xpack.uiActionsEnhanced.components.DiscoverDrilldownConfig.chooseIndexPattern', + { + defaultMessage: 'Choose destination index pattern', + } +); diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/index.ts new file mode 100644 index 00000000000000..b975a73e55621f --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_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 './discover_drilldown_config'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/index.ts new file mode 100644 index 00000000000000..b975a73e55621f --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/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 './discover_drilldown_config'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/constants.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/constants.ts new file mode 100644 index 00000000000000..518642866c2b58 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_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 SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx new file mode 100644 index 00000000000000..8417e17bab6d27 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx @@ -0,0 +1,83 @@ +/* + * 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 { StartDependencies as Start } from '../plugin'; +import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; +import { StartServicesGetter } from '../../../../../src/plugins/kibana_utils/public'; +import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; +import { CollectConfigContainer } from './collect_config_container'; +import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; +import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { txtGoToDiscover } from './i18n'; + +const isOutputWithIndexPatterns = ( + output: unknown +): output is { indexPatterns: Array<{ id: string }> } => { + if (!output || typeof output !== 'object') return false; + return Array.isArray((output as any).indexPatterns); +}; + +export interface Params { + start: StartServicesGetter>; +} + +export class DashboardToDiscoverDrilldown + implements Drilldown { + constructor(protected readonly params: Params) {} + + public readonly id = SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN; + + public readonly order = 10; + + public readonly getDisplayName = () => txtGoToDiscover; + + public readonly euiIcon = 'discoverApp'; + + private readonly ReactCollectConfig: React.FC = props => ( + + ); + + public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); + + public readonly createConfig = () => ({ + customIndexPattern: false, + carryFiltersAndQuery: true, + carryTimeRange: true, + }); + + public readonly isConfigValid = (config: Config): config is Config => { + if (config.customIndexPattern && !config.indexPatternId) return false; + return true; + }; + + private readonly getPath = async (config: Config, context: ActionContext): Promise => { + let indexPatternId = + !!config.customIndexPattern && !!config.indexPatternId ? config.indexPatternId : ''; + + if (!indexPatternId && !!context.embeddable) { + const output = context.embeddable!.getOutput(); + if (isOutputWithIndexPatterns(output) && output.indexPatterns.length > 0) { + indexPatternId = output.indexPatterns[0].id; + } + } + + const index = indexPatternId ? `,index:'${indexPatternId}'` : ''; + return `#/discover?_g=(filters:!(),refreshInterval:(pause:!f,value:900000),time:(from:now-7d,to:now))&_a=(columns:!(_source),filters:!()${index},interval:auto,query:(language:kuery,query:''),sort:!())`; + }; + + public readonly getHref = async (config: Config, context: ActionContext): Promise => { + return `kibana${await this.getPath(config, context)}`; + }; + + public readonly execute = async (config: Config, context: ActionContext) => { + const path = await this.getPath(config, context); + + await this.params.start().core.application.navigateToApp('kibana', { + path, + }); + }; +} diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/i18n.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/i18n.ts new file mode 100644 index 00000000000000..3e92a9f3f1fe4b --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_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 txtGoToDiscover = i18n.translate('xpack.uiActionsEnhanced.drilldown.goToDiscover', { + defaultMessage: 'Go to Discover (example)', +}); diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts new file mode 100644 index 00000000000000..7016c5147ef87e --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_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 { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; +export { + DashboardToDiscoverDrilldown, + Params as DashboardToDiscoverDrilldownParams, +} from './drilldown'; +export { + PlaceContext as DashboardToDiscoverPlaceContext, + ActionContext as DashboardToDiscoverActionContext, + Config as DashboardToDiscoverConfig, +} from './types'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts new file mode 100644 index 00000000000000..0f549b899859e4 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.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 { + RangeSelectTriggerContext, + ValueClickTriggerContext, + EmbeddableContext, +} from '../../../../../src/plugins/embeddable/public'; +import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; + +export type PlaceContext = EmbeddableContext; +export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; + +export interface Config { + /** + * Whether to use a user selected index pattern, stored in `indexPatternId` field. + */ + customIndexPattern: boolean; + + /** + * ID of index pattern picked by user in UI. If not set, drilldown will use + * the index pattern of the visualization. + */ + indexPatternId?: string; + + /** + * Whether to carry over source dashboard filters and query. + */ + carryFiltersAndQuery: boolean; + + /** + * Whether to carry over source dashboard time range. + */ + carryTimeRange: boolean; +} + +export type CollectConfigProps = UiActionsCollectConfigProps; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx new file mode 100644 index 00000000000000..8cbb1a1de8ee04 --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -0,0 +1,89 @@ +/* + * 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, EuiSwitch, EuiFieldText } from '@elastic/eui'; +import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; +import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { + EmbeddableContext, + RangeSelectTriggerContext, + ValueClickTriggerContext, +} from '../../../../../src/plugins/embeddable/public'; +import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; + +export type PlaceContext = EmbeddableContext; +export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; + +export interface Config { + url: string; + openInNewTab: boolean; +} + +export type CollectConfigProps = UiActionsCollectConfigProps; + +const SAMPLE_DASHBOARD_TO_URL_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_URL_DRILLDOWN'; + +export class DashboardToUrlDrilldown implements Drilldown { + public readonly id = SAMPLE_DASHBOARD_TO_URL_DRILLDOWN; + + public readonly order = 8; + + public readonly getDisplayName = () => 'Go to URL (example)'; + + public readonly euiIcon = 'link'; + + private readonly ReactCollectConfig: React.FC = ({ config, onConfig }) => ( + <> + + onConfig({ ...config, url: event.target.value })} + /> + + + onConfig({ ...config, openInNewTab: !config.openInNewTab })} + /> + + + ); + + public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); + + public readonly createConfig = () => ({ + url: '', + openInNewTab: false, + }); + + public readonly isConfigValid = (config: Config): config is Config => { + return !!config.url && typeof config.url === 'string'; + }; + + /** + * `getHref` is need to support mouse middle-click and Cmd + Click behavior + * to open a link in new tab. + */ + public readonly getHref = async (config: Config, context: ActionContext) => { + return config.url; + }; + + public readonly execute = async (config: Config, context: ActionContext) => { + const url = await this.getHref(config, context); + + if (config.openInNewTab) { + window.open(url, '_blank'); + } else { + window.location.href = url; + } + }; +} diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index a4c43753c82479..cf214ebd51596d 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -7,22 +7,32 @@ import { Plugin, CoreSetup, CoreStart } from '../../../../src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { DrilldownsSetup, DrilldownsStart } from '../../../../x-pack/plugins/drilldowns/public'; +import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown'; +import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown'; +import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown'; +import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; export interface SetupDependencies { data: DataPublicPluginSetup; + drilldowns: DrilldownsSetup; uiActions: UiActionsSetup; } export interface StartDependencies { data: DataPublicPluginStart; + drilldowns: DrilldownsStart; uiActions: UiActionsStart; } export class UiActionsEnhancedExamplesPlugin implements Plugin { - public setup(core: CoreSetup, plugins: SetupDependencies) { - // eslint-disable-next-line - console.log('ui_actions_enhanced_examples'); + public setup(core: CoreSetup, { drilldowns }: SetupDependencies) { + const start = createStartServicesGetter(core.getStartServices); + + drilldowns.registerDrilldown(new DashboardHelloWorldDrilldown()); + drilldowns.registerDrilldown(new DashboardToUrlDrilldown()); + drilldowns.registerDrilldown(new DashboardToDiscoverDrilldown({ start })); } public start(core: CoreStart, plugins: StartDependencies) {} 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 502f7a50c854f8..54dba4d3e4d0f3 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 @@ -175,7 +175,7 @@ const ActionFactorySelector: React.FC = ({ return ( {[...actionFactories] - .sort((f1, f2) => f1.order - f2.order) + .sort((f1, f2) => f2.order - f1.order) .map(actionFactory => ( ({ id: factoryId, + order, CollectConfig, createConfig, isConfigValid, @@ -53,6 +54,7 @@ export class DrilldownService { ExecutionContext > = { id: factoryId, + order, CollectConfig, createConfig, isConfigValid, diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 20caecf501a3bd..8a5aa152cf8448 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -34,6 +34,12 @@ export interface DrilldownDefinition< */ id: string; + /** + * Determines the display order of the drilldowns in the flyout picker. + * Higher numbers are displayed first. + */ + order?: number; + /** * Function that returns default config for this drilldown. */ From e858ca0168bd2b4deff2644eb933a137cd23f3ba Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 10:32:55 +0200 Subject: [PATCH 104/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20store=20events?= =?UTF-8?q?=20in=20.enhancements=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/lib/embeddables/i_embeddable.ts | 4 +- .../embeddable_action_storage.test.ts | 39 +++++------ .../embeddables/embeddable_action_storage.ts | 70 ++++++++++++------- .../embeddable_enhanced/public/plugin.ts | 7 +- 4 files changed, 71 insertions(+), 49 deletions(-) diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 9a452339672950..90f8d2e0098436 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -36,9 +36,9 @@ export interface EmbeddableInput { hidePanelTitles?: boolean; /** - * Reserved key for `ui_actions` events. + * Reserved key for enhancements added by X-Pack plugins. */ - events?: Array<{ eventId: string }>; + enhancements?: unknown; /** * List of action IDs that this embeddable should not render. diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 4eeda32a2de76e..f8b3a9dfb92d07 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Embeddable, ViewMode } from '../../../../../src/plugins/embeddable/public'; import { - Embeddable, - EmbeddableInput, - ViewMode, -} from '../../../../../src/plugins/embeddable/public'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; + EmbeddableActionStorage, + EmbeddableWithDynamicActionsInput, +} from './embeddable_action_storage'; import { UiActionsEnhancedSerializedEvent } from '../../../advanced_ui_actions/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; -class TestEmbeddable extends Embeddable { +class TestEmbeddable extends Embeddable { public readonly type = 'test'; constructor() { super({ id: 'test', viewMode: ViewMode.VIEW }, {}); @@ -38,12 +37,12 @@ describe('EmbeddableActionStorage', () => { action: {} as any, }; - const events1 = embeddable.getInput().events || []; + const events1 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events1).toEqual([]); await storage.create(event); - const events2 = embeddable.getInput().events || []; + const events2 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events2).toEqual([event]); }); @@ -84,18 +83,18 @@ describe('EmbeddableActionStorage', () => { action: {} as any, }; - const events1 = embeddable.getInput().events || []; + const events1 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events1).toEqual([]); await storage.create(event1); - const events2 = embeddable.getInput().events || []; + const events2 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events2).toEqual([event1]); await storage.create(event2); await storage.create(event3); - const events3 = embeddable.getInput().events || []; + const events3 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events3).toEqual([event1, event2, event3]); }); @@ -147,7 +146,7 @@ describe('EmbeddableActionStorage', () => { await storage.create(event1); await storage.update(event2); - const events = embeddable.getInput().events || []; + const events = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events).toEqual([event2]); }); @@ -188,17 +187,17 @@ describe('EmbeddableActionStorage', () => { await storage.create(event2); await storage.create(event3); - const events1 = embeddable.getInput().events || []; + const events1 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events1).toEqual([event1, event2, event3]); await storage.update(event22); - const events2 = embeddable.getInput().events || []; + const events2 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events2).toEqual([event1, event22, event3]); await storage.update(event2); - const events3 = embeddable.getInput().events || []; + const events3 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events3).toEqual([event1, event2, event3]); }); @@ -265,7 +264,7 @@ describe('EmbeddableActionStorage', () => { await storage.create(event); await storage.remove(event.eventId); - const events = embeddable.getInput().events || []; + const events = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events).toEqual([]); }); @@ -299,22 +298,22 @@ describe('EmbeddableActionStorage', () => { await storage.create(event2); await storage.create(event3); - const events1 = embeddable.getInput().events || []; + const events1 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events1).toEqual([event1, event2, event3]); await storage.remove(event2.eventId); - const events2 = embeddable.getInput().events || []; + const events2 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events2).toEqual([event1, event3]); await storage.remove(event3.eventId); - const events3 = embeddable.getInput().events || []; + const events3 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events3).toEqual([event1]); await storage.remove(event1.eventId); - const events4 = embeddable.getInput().events || []; + const events4 = embeddable.getInput().enhancements?.dynamicActions?.events || []; expect(events4).toEqual([]); }); diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index d02d39b5b172c6..dcb44323f6d111 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -8,16 +8,45 @@ import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage, UiActionsEnhancedSerializedEvent as SerializedEvent, } from '../../../advanced_ui_actions/public'; -import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { + EmbeddableInput, + EmbeddableOutput, + IEmbeddable, +} from '../../../../../src/plugins/embeddable/public'; + +export interface EmbeddableWithDynamicActionsInput extends EmbeddableInput { + enhancements?: { + dynamicActions?: { + events: SerializedEvent[]; + }; + }; +} + +export type EmbeddableWithDynamicActions< + I extends EmbeddableWithDynamicActionsInput = EmbeddableWithDynamicActionsInput, + O extends EmbeddableOutput = EmbeddableOutput +> = IEmbeddable; export class EmbeddableActionStorage extends AbstractActionStorage { - constructor(private readonly embbeddable: IEmbeddable) { + constructor(private readonly embbeddable: EmbeddableWithDynamicActions) { super(); } - async create(event: SerializedEvent) { + private put(input: EmbeddableWithDynamicActionsInput, events: SerializedEvent[]) { + this.embbeddable.updateInput({ + enhancements: { + ...(input.enhancements || {}), + dynamicActions: { + ...(input.enhancements?.dynamicActions || {}), + events, + }, + }, + }); + } + + public async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = input.enhancements?.dynamicActions?.events || []; const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -27,14 +56,12 @@ export class EmbeddableActionStorage extends AbstractActionStorage { ); } - this.embbeddable.updateInput({ - events: [...events, event], - }); + this.put(input, [...events, event]); } - async update(event: SerializedEvent) { + public async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = input.enhancements?.dynamicActions?.events || []; const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -45,14 +72,12 @@ export class EmbeddableActionStorage extends AbstractActionStorage { ); } - this.embbeddable.updateInput({ - events: [...events.slice(0, index), event, ...events.slice(index + 1)], - }); + this.put(input, [...events.slice(0, index), event, ...events.slice(index + 1)]); } - async remove(eventId: string) { + public async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = input.enhancements?.dynamicActions?.events || []; const index = events.findIndex(event => eventId === event.eventId); if (index === -1) { @@ -63,14 +88,12 @@ export class EmbeddableActionStorage extends AbstractActionStorage { ); } - this.embbeddable.updateInput({ - events: [...events.slice(0, index), ...events.slice(index + 1)], - }); + this.put(input, [...events.slice(0, index), ...events.slice(index + 1)]); } - async read(eventId: string): Promise { + public async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = (input.events || []) as SerializedEvent[]; + const events = input.enhancements?.dynamicActions?.events || []; const event = events.find(ev => eventId === ev.eventId); if (!event) { @@ -83,12 +106,9 @@ export class EmbeddableActionStorage extends AbstractActionStorage { return event; } - private __list() { + public async list(): Promise { const input = this.embbeddable.getInput(); - return (input.events || []) as SerializedEvent[]; - } - - async list(): Promise { - return this.__list(); + const events = input.enhancements?.dynamicActions?.events || []; + return events; } } diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 28503e83c9547e..ad99b034963bc0 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -19,7 +19,10 @@ import { PANEL_NOTIFICATION_TRIGGER, } from '../../../../src/plugins/embeddable/public'; import { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; -import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; +import { + EmbeddableActionStorage, + EmbeddableWithDynamicActions, +} from './embeddables/embeddable_action_storage'; import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, @@ -108,7 +111,7 @@ export class EmbeddableEnhancedPlugin ): EnhancedEmbeddable { const enhancedEmbeddable = embeddable as EnhancedEmbeddable; - const storage = new EmbeddableActionStorage(embeddable); + const storage = new EmbeddableActionStorage(embeddable as EmbeddableWithDynamicActions); const dynamicActions = new DynamicActionManager({ isCompatible: async (context: unknown) => (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId, From e2a78d2689191c8ec4c42910015af74350a7483d Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 10:33:48 +0200 Subject: [PATCH 105/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20comm?= =?UTF-8?q?ent=20to=20range=20trigger=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/triggers/select_range_trigger.ts | 2 ++ 1 file changed, 2 insertions(+) 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 9758508dc3dacc..c7c998907381ab 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,8 @@ import { Trigger } from '.'; export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { id: SELECT_RANGE_TRIGGER, + // This is empty string to hide title of ui_actions context menu that appears + // when this trigger is executed. title: '', description: 'Applies a range filter', }; From 5023ac43babcf88be5d995779daa6cbd3da3f46d Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 13:26:08 +0200 Subject: [PATCH 106/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20Confi?= =?UTF-8?q?gurable=20interface=20into=20kibana=5Futils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/kibana_utils/public/index.ts | 1 + .../public/ui}/configurable.ts | 2 +- src/plugins/kibana_utils/public/ui/index.ts | 20 +++++++++++++++++++ src/plugins/ui_actions/public/index.ts | 6 +----- src/plugins/ui_actions/public/util/index.ts | 1 - .../dashboard_hello_world_drilldown/index.tsx | 4 ++-- .../dashboard_to_discover_drilldown/types.ts | 4 ++-- .../dashboard_to_url_drilldown/index.tsx | 4 ++-- .../components/action_wizard/test_data.tsx | 2 +- .../public/dynamic_actions/action_factory.ts | 2 +- .../action_factory_definition.ts | 2 +- .../advanced_ui_actions/public/index.ts | 4 ---- .../advanced_ui_actions/public/util/index.ts | 10 ---------- .../dashboard_drilldowns_services.ts | 2 +- .../components/collect_config_container.tsx | 8 ++++---- x-pack/plugins/drilldowns/public/types.ts | 2 +- 16 files changed, 38 insertions(+), 36 deletions(-) rename src/plugins/{ui_actions/public/util => kibana_utils/public/ui}/configurable.ts (96%) create mode 100644 src/plugins/kibana_utils/public/ui/index.ts delete mode 100644 x-pack/plugins/advanced_ui_actions/public/util/index.ts diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index b7dcafc40d1cd2..ae8e177dfa66dc 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -74,6 +74,7 @@ export { StartSyncStateFnType, StopSyncStateFnType, } from './state_sync'; +export { Configurable, CollectConfigProps } from './ui'; export { removeQueryParam, redirectWhenMissing } from './history'; export { applyDiff } from './state_management/utils/diff_object'; export { createStartServicesGetter } from './core/create_start_service_getter'; diff --git a/src/plugins/ui_actions/public/util/configurable.ts b/src/plugins/kibana_utils/public/ui/configurable.ts similarity index 96% rename from src/plugins/ui_actions/public/util/configurable.ts rename to src/plugins/kibana_utils/public/ui/configurable.ts index 0a5a703abd0c18..a4a9f09c1c0e09 100644 --- a/src/plugins/ui_actions/public/util/configurable.ts +++ b/src/plugins/kibana_utils/public/ui/configurable.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/public'; +import { UiComponent } from '../../common/ui/ui_component'; /** * Represents something that can be configured by user using UI. diff --git a/src/plugins/kibana_utils/public/ui/index.ts b/src/plugins/kibana_utils/public/ui/index.ts new file mode 100644 index 00000000000000..54d47ac7e980f9 --- /dev/null +++ b/src/plugins/kibana_utils/public/ui/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 * from './configurable'; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 13a6bb505ef136..a9b413fb36542d 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -33,11 +33,7 @@ export { IncompatibleActionError, } from './actions'; export { buildContextMenuForActions } from './context_menu'; -export { - Presentable as UiActionsPresentable, - Configurable as UiActionsConfigurable, - CollectConfigProps as UiActionsCollectConfigProps, -} from './util'; +export { Presentable as UiActionsPresentable } from './util'; export { Trigger, TriggerContext, diff --git a/src/plugins/ui_actions/public/util/index.ts b/src/plugins/ui_actions/public/util/index.ts index 53c6109cac4ca0..a6943e54f016cb 100644 --- a/src/plugins/ui_actions/public/util/index.ts +++ b/src/plugins/ui_actions/public/util/index.ts @@ -18,4 +18,3 @@ */ export * from './presentable'; -export * from './configurable'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx index 0ef8528a55ec66..5e948684e2cf1e 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx @@ -13,7 +13,7 @@ import { RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../src/plugins/embeddable/public'; -import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; +import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public'; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -33,7 +33,7 @@ export class DashboardHelloWorldDrilldown public readonly euiIcon = 'cheer'; - private readonly ReactCollectConfig: React.FC> = ({ + private readonly ReactCollectConfig: React.FC> = ({ config, onConfig, }) => ( diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts index 0f549b899859e4..c312efc048fbe9 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts @@ -9,7 +9,7 @@ import { ValueClickTriggerContext, EmbeddableContext, } from '../../../../../src/plugins/embeddable/public'; -import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; +import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public'; export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -37,4 +37,4 @@ export interface Config { carryTimeRange: boolean; } -export type CollectConfigProps = UiActionsCollectConfigProps; +export type CollectConfigProps = CollectConfigProps; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index 8cbb1a1de8ee04..301bf2e8061fc3 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -13,7 +13,7 @@ import { RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../src/plugins/embeddable/public'; -import { UiActionsCollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; +import { CollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -23,7 +23,7 @@ export interface Config { openInNewTab: boolean; } -export type CollectConfigProps = UiActionsCollectConfigProps; +export type CollectConfigProps = CollectConfigProps; const SAMPLE_DASHBOARD_TO_URL_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_URL_DRILLDOWN'; 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 102aee1878bc5d..c3e749f163c949 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 @@ -9,7 +9,7 @@ import { EuiFieldText, EuiFormRow, EuiSelect, EuiSwitch } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { ActionWizard } from './action_wizard'; import { ActionFactoryDefinition, ActionFactory } from '../../dynamic_actions'; -import { CollectConfigProps } from '../../util'; +import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; type ActionBaseConfig = object; diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts index cebdfe1dd4444d..f1aef5deff49e0 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory.ts @@ -10,7 +10,7 @@ import { UiActionsPresentable as Presentable, } from '../../../../../src/plugins/ui_actions/public'; import { ActionFactoryDefinition } from './action_factory_definition'; -import { Configurable } from '../util'; +import { Configurable } from '../../../../../src/plugins/kibana_utils/public'; import { SerializedAction } from './types'; export class ActionFactory< diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts index fab926f8e0030b..d3751fe8116655 100644 --- a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/action_factory_definition.ts @@ -8,7 +8,7 @@ import { UiActionsActionDefinition as ActionDefinition, UiActionsPresentable as Presentable, } from '../../../../../src/plugins/ui_actions/public'; -import { Configurable } from '../util'; +import { Configurable } from '../../../../../src/plugins/kibana_utils/public'; import { SerializedAction } from './types'; /** diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 79fb03e9adbe63..03ff9ba5798116 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -24,10 +24,6 @@ export { SerializedAction as UiActionsEnhancedSerializedAction, SerializedEvent as UiActionsEnhancedSerializedEvent, } from './dynamic_actions'; -export { - Configurable as AdvancedUiActionsConfigurable, - CollectConfigProps as AdvancedUiActionsCollectConfigProps, -} from './util'; export { AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, diff --git a/x-pack/plugins/advanced_ui_actions/public/util/index.ts b/x-pack/plugins/advanced_ui_actions/public/util/index.ts deleted file mode 100644 index fd3ab89973348b..00000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/util/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - UiActionsConfigurable as Configurable, - UiActionsCollectConfigProps as CollectConfigProps, -} from '../../../../../src/plugins/ui_actions/public'; 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 fb8f23a0b39c4d..81eaa03d99aa60 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 { } from './actions'; import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown'; -import { createStartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public/'; +import { createStartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index a75f459bbb100c..90684adcb81a6a 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -10,7 +10,7 @@ import { debounce, findIndex } from 'lodash'; import { CoreStart, SimpleSavedObject } from '../../../../../../../../src/core/public'; import { DashboardDrilldownConfig } from './dashboard_drilldown_config'; import { txtDestinationDashboardNotFound } from './i18n'; -import { UiActionsCollectConfigProps } from '../../../../../../../../src/plugins/ui_actions/public'; +import { CollectConfigProps } from '../../../../../../../../src/plugins/kibana_utils/public'; import { DrilldownFactoryContext } from '../../../../../../drilldowns/public'; import { Config } from '../types'; import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; @@ -35,7 +35,7 @@ const dashboardSavedObjectToMenuItem = ( label: savedObject.attributes.title, }); -interface CollectConfigProps extends UiActionsCollectConfigProps { +interface DashboardDrilldownCollectConfigProps extends CollectConfigProps { deps: { getSavedObjectsClient: () => CoreStart['savedObjects']['client']; }; @@ -53,7 +53,7 @@ interface CollectConfigContainerState { } export class CollectConfigContainer extends React.Component< - CollectConfigProps, + DashboardDrilldownCollectConfigProps, CollectConfigContainerState > { private isMounted = true; @@ -65,7 +65,7 @@ export class CollectConfigContainer extends React.Component< error: undefined, }; - constructor(props: CollectConfigProps) { + constructor(props: DashboardDrilldownCollectConfigProps) { super(props); this.debouncedLoadDashboards = debounce(this.loadDashboards.bind(this), 500); } diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts index 8a5aa152cf8448..ec282f611af720 100644 --- a/x-pack/plugins/drilldowns/public/types.ts +++ b/x-pack/plugins/drilldowns/public/types.ts @@ -57,7 +57,7 @@ export interface DrilldownDefinition< * ```tsx * import React from 'react'; * import { uiToReactComponent } from 'src/plugins/kibana_utils'; - * import { UiActionsCollectConfigProps as CollectConfigProps } from 'src/plugins/ui_actions/public'; + * import { CollectConfigProps } from 'src/plugins/kibana_utils/public'; * * type Props = CollectConfigProps; * From 7f53aeb26499d8680bfe57c51ab041e2751040c1 Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 13:29:04 +0200 Subject: [PATCH 107/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20simplify=20inte?= =?UTF-8?q?rnal=20component=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/action_wizard/action_wizard.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 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 54dba4d3e4d0f3..9c2e34d157d705 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 @@ -18,9 +18,6 @@ import { txtChangeButton } from './i18n'; import './action_wizard.scss'; import { ActionFactory } from '../../dynamic_actions'; -type ActionBaseConfig = object; -type ActionFactoryBaseContext = object; - export interface ActionWizardProps { /** * List of available action factories @@ -42,17 +39,17 @@ export interface ActionWizardProps { /** * current config for currently selected action factory */ - config?: ActionBaseConfig; + config?: object; /** * config changed */ - onConfigChange: (config: ActionBaseConfig) => void; + onConfigChange: (config: object) => void; /** * Context will be passed into ActionFactory's methods */ - context: ActionFactoryBaseContext; + context: object; } export const ActionWizard: React.FC = ({ @@ -98,9 +95,9 @@ export const ActionWizard: React.FC = ({ interface SelectedActionFactoryProps { actionFactory: ActionFactory; - config: ActionBaseConfig; - context: ActionFactoryBaseContext; - onConfigChange: (config: ActionBaseConfig) => void; + config: object; + context: object; + onConfigChange: (config: object) => void; showDeselect: boolean; onDeselect: () => void; } @@ -155,7 +152,7 @@ const SelectedActionFactory: React.FC = ({ interface ActionFactorySelectorProps { actionFactories: ActionFactory[]; - context: ActionFactoryBaseContext; + context: object; onActionFactorySelected: (actionFactory: ActionFactory) => void; } From 11676f93bb85b32b93872285b3ce250fe04d7c2b Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 16:29:15 +0200 Subject: [PATCH 108/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20regis?= =?UTF-8?q?terDrilldwon()=20to=20advanced=5Fui=5Factions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/kibana_utils/public/index.ts | 2 +- .../ui_actions_enhanced_examples/kibana.json | 2 +- .../dashboard_hello_world_drilldown/index.tsx | 2 +- .../drilldown.tsx | 2 +- .../dashboard_to_discover_drilldown/types.ts | 4 +- .../dashboard_to_url_drilldown/index.tsx | 6 +- .../public/plugin.ts | 23 ++-- .../drilldown_action_factory_context.ts | 23 ++++ .../public/drilldowns/drilldown_definition.ts | 114 ++++++++++++++++++ .../public/drilldowns}/index.ts | 3 +- .../advanced_ui_actions/public/index.ts | 8 +- .../advanced_ui_actions/public/mocks.ts | 2 +- .../advanced_ui_actions/public/plugin.ts | 2 +- .../ui_actions_service_enhancements.ts | 49 ++++++++ x-pack/plugins/dashboard_enhanced/kibana.json | 2 +- .../dashboard_enhanced/public/plugin.ts | 8 +- .../flyout_create_drilldown.test.tsx | 11 +- .../flyout_create_drilldown.tsx | 14 +-- .../flyout_edit_drilldown.test.tsx | 19 +-- .../flyout_edit_drilldown.tsx | 14 +-- .../dashboard_drilldowns_services.ts | 34 ++---- .../components/collect_config_container.tsx | 19 +-- .../drilldown.test.tsx | 37 ++++-- .../drilldown.tsx | 56 ++++----- x-pack/plugins/drilldowns/public/index.ts | 2 - x-pack/plugins/drilldowns/public/plugin.ts | 11 +- .../public/services/drilldown_service.ts | 85 ------------- 27 files changed, 330 insertions(+), 224 deletions(-) create mode 100644 x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts create mode 100644 x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts rename x-pack/plugins/{drilldowns/public/services => advanced_ui_actions/public/drilldowns}/index.ts (72%) delete mode 100644 x-pack/plugins/drilldowns/public/services/drilldown_service.ts diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index ae8e177dfa66dc..3d8a4414de70c8 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -77,7 +77,7 @@ export { export { Configurable, CollectConfigProps } from './ui'; export { removeQueryParam, redirectWhenMissing } from './history'; export { applyDiff } from './state_management/utils/diff_object'; -export { createStartServicesGetter } from './core/create_start_service_getter'; +export { createStartServicesGetter, StartServicesGetter } from './core/create_start_service_getter'; /** dummy plugin, we just want kibanaUtils to have its own bundle */ export function plugin() { diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.json b/x-pack/examples/ui_actions_enhanced_examples/kibana.json index 622f45b016bb3c..e220cdd5cd297b 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.json +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.json @@ -5,6 +5,6 @@ "configPath": ["ui_actions_enhanced_examples"], "server": false, "ui": true, - "requiredPlugins": ["uiActions", "drilldowns", "data"], + "requiredPlugins": ["advancedUiActions", "data"], "optionalPlugins": [] } diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx index 5e948684e2cf1e..75e85d0e302625 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; -import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { EmbeddableContext, RangeSelectTriggerContext, diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx index 8417e17bab6d27..7ab56461507446 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx @@ -11,7 +11,7 @@ import { StartServicesGetter } from '../../../../../src/plugins/kibana_utils/pub import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; import { CollectConfigContainer } from './collect_config_container'; import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; -import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { txtGoToDiscover } from './i18n'; const isOutputWithIndexPatterns = ( diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts index c312efc048fbe9..f7bb3885f6f62c 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts @@ -9,7 +9,7 @@ import { ValueClickTriggerContext, EmbeddableContext, } from '../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public'; +import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public'; export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -37,4 +37,4 @@ export interface Config { carryTimeRange: boolean; } -export type CollectConfigProps = CollectConfigProps; +export type CollectConfigProps = CollectConfigPropsBase; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index 301bf2e8061fc3..ec968807f5720b 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { EuiFormRow, EuiSwitch, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; -import { DrilldownDefinition as Drilldown } from '../../../../plugins/drilldowns/public'; +import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { EmbeddableContext, RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps } from '../../../../../src/plugins/ui_actions/public'; +import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public'; export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -23,7 +23,7 @@ export interface Config { openInNewTab: boolean; } -export type CollectConfigProps = CollectConfigProps; +export type CollectConfigProps = CollectConfigPropsBase; const SAMPLE_DASHBOARD_TO_URL_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_URL_DRILLDOWN'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index cf214ebd51596d..0d4f274caf57ff 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -5,9 +5,11 @@ */ import { Plugin, CoreSetup, CoreStart } from '../../../../src/core/public'; -import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; -import { DrilldownsSetup, DrilldownsStart } from '../../../../x-pack/plugins/drilldowns/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsStart, +} from '../../../../x-pack/plugins/advanced_ui_actions/public'; import { DashboardHelloWorldDrilldown } from './dashboard_hello_world_drilldown'; import { DashboardToUrlDrilldown } from './dashboard_to_url_drilldown'; import { DashboardToDiscoverDrilldown } from './dashboard_to_discover_drilldown'; @@ -15,24 +17,25 @@ import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/ export interface SetupDependencies { data: DataPublicPluginSetup; - drilldowns: DrilldownsSetup; - uiActions: UiActionsSetup; + advancedUiActions: AdvancedUiActionsSetup; } export interface StartDependencies { data: DataPublicPluginStart; - drilldowns: DrilldownsStart; - uiActions: UiActionsStart; + advancedUiActions: AdvancedUiActionsStart; } export class UiActionsEnhancedExamplesPlugin implements Plugin { - public setup(core: CoreSetup, { drilldowns }: SetupDependencies) { + public setup( + core: CoreSetup, + { advancedUiActions: uiActions }: SetupDependencies + ) { const start = createStartServicesGetter(core.getStartServices); - drilldowns.registerDrilldown(new DashboardHelloWorldDrilldown()); - drilldowns.registerDrilldown(new DashboardToUrlDrilldown()); - drilldowns.registerDrilldown(new DashboardToDiscoverDrilldown({ start })); + uiActions.registerDrilldown(new DashboardHelloWorldDrilldown()); + uiActions.registerDrilldown(new DashboardToUrlDrilldown()); + uiActions.registerDrilldown(new DashboardToDiscoverDrilldown({ start })); } public start(core: CoreStart, plugins: StartDependencies) {} diff --git a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts new file mode 100644 index 00000000000000..b1a70948c9450b --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.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. + */ + +/** + * Context object used when creating a drilldown. + */ +export interface DrilldownActionFactoryContext { + /** + * 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[]; +} diff --git a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts new file mode 100644 index 00000000000000..21f7fc0e7c47c0 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts @@ -0,0 +1,114 @@ +/* + * 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 { ActionFactoryDefinition } from '../dynamic_actions'; +import { DrilldownActionFactoryContext } from './drilldown_action_factory_context'; + +/** + * 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, + PlaceContext extends object = object, + ExecutionContext extends object = object +> { + /** + * Globally unique identifier for this drilldown. + */ + id: string; + + /** + * Determines the display order of the drilldowns in the flyout picker. + * Higher numbers are displayed first. + */ + order?: number; + + /** + * Function that returns default config for this drilldown. + */ + createConfig: ActionFactoryDefinition< + Config, + DrilldownActionFactoryContext, + 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 { CollectConfigProps } from 'src/plugins/kibana_utils/public'; + * + * type Props = CollectConfigProps; + * + * const ReactCollectConfig: React.FC = () => { + * return
Collecting config...'
; + * }; + * + * export const CollectConfig = uiToReactComponent(ReactCollectConfig); + * ``` + */ + CollectConfig: ActionFactoryDefinition< + Config, + DrilldownActionFactoryContext, + ExecutionContext + >['CollectConfig']; + + /** + * A validator function for the config object. Should always return a boolean + * given any input. + */ + isConfigValid: ActionFactoryDefinition< + Config, + DrilldownActionFactoryContext, + ExecutionContext + >['isConfigValid']; + + /** + * Name of EUI icon to display when showing this drilldown to user. + */ + euiIcon?: string; + + /** + * Should return an internationalized name of the drilldown, which will be + * displayed to the user. + */ + getDisplayName: () => string; + + /** + * 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 + * `UIAction` of this drilldown is being executed in. + */ + execute(config: Config, context: ExecutionContext): void; + + /** + * A link where drilldown should navigate on middle click or Ctrl + click. + */ + getHref?(config: Config, context: ExecutionContext): Promise; +} diff --git a/x-pack/plugins/drilldowns/public/services/index.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts similarity index 72% rename from x-pack/plugins/drilldowns/public/services/index.ts rename to x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts index 44472b18a53172..3950e9b46ba336 100644 --- a/x-pack/plugins/drilldowns/public/services/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './drilldown_service'; +export * from './drilldown_action_factory_context'; +export * from './drilldown_definition'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 03ff9ba5798116..e7bc7c50bd6219 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -23,12 +23,14 @@ export { ActionFactory as AdvancedUiActionsActionFactory, SerializedAction as UiActionsEnhancedSerializedAction, SerializedEvent as UiActionsEnhancedSerializedEvent, -} from './dynamic_actions'; - -export { AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, DynamicActionManager as UiActionsEnhancedDynamicActionManager, DynamicActionManagerParams as UiActionsEnhancedDynamicActionManagerParams, DynamicActionManagerState as UiActionsEnhancedDynamicActionManagerState, MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, } from './dynamic_actions'; + +export { + DrilldownDefinition as UiActionsEnhancedDrilldownDefinition, + DrilldownActionFactoryContext as UiActionsEnhancedDrilldownActionFactoryContext, +} from './drilldowns'; diff --git a/x-pack/plugins/advanced_ui_actions/public/mocks.ts b/x-pack/plugins/advanced_ui_actions/public/mocks.ts index 39d88b017ca074..65fde12755beb8 100644 --- a/x-pack/plugins/advanced_ui_actions/public/mocks.ts +++ b/x-pack/plugins/advanced_ui_actions/public/mocks.ts @@ -17,7 +17,7 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { ...uiActionsPluginMock.createSetupContract(), - registerActionFactory: jest.fn(), + registerDrilldown: jest.fn(), }; return setupContract; }; diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts index cb2bbdba0a313b..f042130158aecf 100644 --- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts +++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts @@ -44,7 +44,7 @@ interface StartDependencies { export interface SetupContract extends UiActionsSetup, - Pick {} + Pick {} export interface StartContract extends UiActionsStart, diff --git a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts index 87bbc803964931..07e00e4a24d151 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts @@ -6,6 +6,7 @@ import { ActionFactoryRegistry } from '../types'; import { ActionFactory, ActionFactoryDefinition } from '../dynamic_actions'; +import { DrilldownDefinition, DrilldownActionFactoryContext } from '../drilldowns'; export interface UiActionsServiceEnhancementsParams { readonly actionFactories?: ActionFactoryRegistry; @@ -54,4 +55,52 @@ export class UiActionsServiceEnhancements { public readonly getActionFactories = (): ActionFactory[] => { return [...this.actionFactories.values()]; }; + + /** + * Convenience method to register a {@link DrilldownDefinition | drilldown}. + */ + public readonly registerDrilldown = < + Config extends object = object, + PlaceContext extends object = object, + ExecutionContext extends object = object + >({ + id: factoryId, + order, + CollectConfig, + createConfig, + isConfigValid, + getDisplayName, + euiIcon, + execute, + getHref, + }: DrilldownDefinition): void => { + const actionFactory: ActionFactoryDefinition< + Config, + DrilldownActionFactoryContext, + ExecutionContext + > = { + id: factoryId, + order, + CollectConfig, + createConfig, + isConfigValid, + getDisplayName, + getIconType: () => euiIcon, + isCompatible: async () => true, + create: serializedAction => ({ + id: '', + type: factoryId, + getIconType: () => euiIcon, + getDisplayName: () => serializedAction.name, + execute: async context => await execute(serializedAction.config, context), + getHref: getHref ? async context => getHref(serializedAction.config, context) : undefined, + }), + } as ActionFactoryDefinition< + Config, + DrilldownActionFactoryContext, + ExecutionContext + >; + + this.registerActionFactory(actionFactory); + }; } diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index d2ef156c820236..f416ca97f71107 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -3,6 +3,6 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["data","uiActions", "embeddable", "dashboard", "drilldowns", "share"], + "requiredPlugins": ["data", "advancedUiActions", "drilldowns", "embeddable", "dashboard", "share"], "configPath": ["xpack", "dashboardEnhanced"] } diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 75d25af2e0132e..772e032289bcee 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -6,25 +6,25 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { SharePluginStart, SharePluginSetup } from '../../../../src/plugins/share/public'; -import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { AdvancedUiActionsSetup, AdvancedUiActionsStart } from '../../advanced_ui_actions/public'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { + advancedUiActions: AdvancedUiActionsSetup; drilldowns: DrilldownsSetup; embeddable: EmbeddableSetup; - uiActions: UiActionsSetup; share: SharePluginSetup; } export interface StartDependencies { + advancedUiActions: AdvancedUiActionsStart; + data: DataPublicPluginStart; drilldowns: DrilldownsStart; embeddable: EmbeddableStart; - uiActions: UiActionsStart; share: SharePluginStart; - data: DataPublicPluginStart; } // eslint-disable-next-line 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 004853febea0e1..ee25027959821e 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 @@ -18,8 +18,15 @@ const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); const actionParams: OpenFlyoutAddDrilldownParams = { - drilldowns: () => drilldowns, - overlays: () => overlays, + start: () => ({ + core: { + overlays, + } as any, + plugins: { + drilldowns, + }, + self: {}, + }), }; test('should create', () => { 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 20eaba91786340..34b84de61bb644 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 @@ -6,18 +6,17 @@ 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 { DrilldownsStart } from '../../../../../../drilldowns/public'; import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; +import { StartDependencies } from '../../../../plugin'; +import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; export interface OpenFlyoutAddDrilldownParams { - overlays: () => CoreStart['overlays']; - drilldowns: () => DrilldownsStart; + start: StartServicesGetter>; } export class FlyoutCreateDrilldownAction implements ActionByType { @@ -50,8 +49,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'create'} 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 index 9423beabae7024..555acf1fca5ffa 100644 --- 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 @@ -17,20 +17,25 @@ const drilldowns = drilldownsPluginMock.createStartContract(); const uiActionsPlugin = uiActionsEnhancedPluginMock.createPlugin(); const uiActions = uiActionsPlugin.doStart(); -uiActionsPlugin.setup.registerActionFactory({ +uiActionsPlugin.setup.registerDrilldown({ id: 'foo', CollectConfig: {} as any, createConfig: () => ({}), isConfigValid: () => true, - create: () => ({ - id: 'test', - execute: async () => {}, - }), + execute: async () => {}, + getDisplayName: () => 'test', }); const actionParams: FlyoutEditDrilldownParams = { - drilldowns: () => drilldowns, - overlays: () => overlays, + start: () => ({ + core: { + overlays, + } as any, + plugins: { + drilldowns, + }, + self: {}, + }), }; test('should create', () => { 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 3a4eb4a4b86446..a4499ba4d757d6 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 @@ -5,23 +5,22 @@ */ 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 { DrilldownsStart } from '../../../../../../drilldowns/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; +import { StartDependencies } from '../../../../plugin'; +import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; export interface FlyoutEditDrilldownParams { - overlays: () => CoreStart['overlays']; - drilldowns: () => DrilldownsStart; + start: StartServicesGetter>; } export class FlyoutEditDrilldownAction implements ActionByType { @@ -48,8 +47,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} placeContext={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 81eaa03d99aa60..0161836b2c5b97 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 @@ -14,7 +14,6 @@ import { OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN, } from './actions'; - import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldown'; import { createStartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; @@ -40,30 +39,19 @@ export class DashboardDrilldownsService { } } - setupDrilldowns(core: CoreSetup, plugins: SetupDependencies) { - const getStartServices = createStartServicesGetter( - core.getStartServices - ); - - const overlays = () => getStartServices().core.overlays; - const drilldowns = () => getStartServices().plugins.drilldowns; - const getSavedObjectsClient = () => getStartServices().core.savedObjects.client; - const getApplicationService = () => getStartServices().core.application; - const getGetUrlGenerator = () => getStartServices().plugins.share.urlGenerators.getUrlGenerator; - const getDataPluginActions = () => getStartServices().plugins.data.actions; + setupDrilldowns( + core: CoreSetup, + { advancedUiActions: uiActions }: SetupDependencies + ) { + const start = createStartServicesGetter(core.getStartServices); - const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); + const actionFlyoutCreateDrilldown = new FlyoutCreateDrilldownAction({ start }); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutCreateDrilldown); - const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ overlays, drilldowns }); - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); + const actionFlyoutEditDrilldown = new FlyoutEditDrilldownAction({ start }); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, actionFlyoutEditDrilldown); - const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ - getSavedObjectsClient, - getGetUrlGenerator, - getApplicationService, - getDataPluginActions, - }); - plugins.drilldowns.registerDrilldown(dashboardToDashboardDrilldown); + const dashboardToDashboardDrilldown = new DashboardToDashboardDrilldown({ start }); + uiActions.registerDrilldown(dashboardToDashboardDrilldown); } } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index 90684adcb81a6a..08fd35184b396e 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -7,13 +7,14 @@ import React from 'react'; import { EuiComboBoxOptionOption } from '@elastic/eui'; import { debounce, findIndex } from 'lodash'; -import { CoreStart, SimpleSavedObject } from '../../../../../../../../src/core/public'; +import { SimpleSavedObject } from '../../../../../../../../src/core/public'; import { DashboardDrilldownConfig } from './dashboard_drilldown_config'; import { txtDestinationDashboardNotFound } from './i18n'; import { CollectConfigProps } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { DrilldownFactoryContext } from '../../../../../../drilldowns/public'; +import { UiActionsEnhancedDrilldownActionFactoryContext as DrilldownFactoryContext } from '../../../../../../advanced_ui_actions/public'; import { Config } from '../types'; import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; +import { Params } from '../drilldown'; const mergeDashboards = ( dashboards: Array>, @@ -36,9 +37,7 @@ const dashboardSavedObjectToMenuItem = ( }); interface DashboardDrilldownCollectConfigProps extends CollectConfigProps { - deps: { - getSavedObjectsClient: () => CoreStart['savedObjects']['client']; - }; + params: Params; context: DrilldownFactoryContext<{ embeddable: IEmbeddable; }>; @@ -115,10 +114,12 @@ export class CollectConfigContainer extends React.Component< } private async loadSelectedDashboard() { - const { config } = this.props; + const { + config, + params: { start }, + } = this.props; if (!config.dashboardId) return; - const savedObjectsClient = this.props.deps.getSavedObjectsClient(); - const savedObject = await savedObjectsClient.get<{ title: string }>( + const savedObject = await start().core.savedObjects.client.get<{ title: string }>( 'dashboard', config.dashboardId ); @@ -149,7 +150,7 @@ export class CollectConfigContainer extends React.Component< private async loadDashboards(searchString?: string) { const currentDashboardId = this.props.context.placeContext.embeddable?.parent?.id; this.setState({ searchString, isLoading: true }); - const savedObjectsClient = this.props.deps.getSavedObjectsClient(); + const savedObjectsClient = this.props.params.start().core.savedObjects.client; const { savedObjects } = await savedObjectsClient.find<{ title: string }>({ type: 'dashboard', search: searchString ? `${searchString}*` : undefined, 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 index 8b69b20cac3e38..2f2417e599bcfd 100644 --- 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 @@ -87,16 +87,33 @@ describe('.execute() & getHref', () => { const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; const drilldown = new DashboardToDashboardDrilldown({ - getApplicationService: () => ({ - navigateToApp, - getUrlForApp, - }), - getGetUrlGenerator: () => () => - createDirectAccessDashboardLinkGenerator(() => - Promise.resolve({ appBasePath: 'test', useHashedUrl: false }) - ) as UrlGeneratorContract, - getDataPluginActions: () => dataPluginActions, - getSavedObjectsClient: () => savedObjectsClient, + start: () => + ({ + core: { + application: { + navigateToApp, + getUrlForApp, + }, + savedObjects: { + client: savedObjectsClient, + }, + }, + plugins: { + advancedUiActions: {}, + data: { + actions: dataPluginActions, + }, + share: { + urlGenerators: { + getUrlGenerator: () => + createDirectAccessDashboardLinkGenerator(() => + Promise.resolve({ appBasePath: 'test', useHashedUrl: false }) + ) as UrlGeneratorContract, + }, + }, + }, + self: {}, + } as any), }); const selectRangeFiltersSpy = jest .spyOn(dataPluginActions, 'createFiltersFromRangeSelectAction') 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 68aac6c2bd360e..eebea9ffb2c07c 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 @@ -5,34 +5,29 @@ */ import React from 'react'; -import { CoreStart } from 'src/core/public'; import { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; -import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../src/plugins/dashboard/public'; import { PlaceContext, ActionContext, Config } from './types'; import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; -import { DrilldownDefinition as Drilldown } from '../../../../../drilldowns/public'; +import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../advanced_ui_actions/public'; import { txtGoToDashboard } from './i18n'; -import { DataPublicPluginStart, esFilters } from '../../../../../../../src/plugins/data/public'; +import { esFilters } from '../../../../../../../src/plugins/data/public'; import { VisualizeEmbeddableContract } from '../../../../../../../src/plugins/visualizations/public'; import { isRangeSelectTriggerContext, isValueClickTriggerContext, } from '../../../../../../../src/plugins/embeddable/public'; +import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public'; +import { StartDependencies } from '../../../plugin'; export interface Params { - getSavedObjectsClient: () => CoreStart['savedObjects']['client']; - getApplicationService: () => Pick; - getGetUrlGenerator: () => SharePluginStart['urlGenerators']['getUrlGenerator']; - getDataPluginActions: () => DataPublicPluginStart['actions']; + start: StartServicesGetter>; } export class DashboardToDashboardDrilldown implements Drilldown> { - constructor(protected readonly params: Params) { - this.getDestinationUrl = this.getDestinationUrl.bind(this); - } + constructor(protected readonly params: Params) {} public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -43,7 +38,7 @@ export class DashboardToDashboardDrilldown public readonly euiIcon = 'dashboardApp'; private readonly ReactCollectConfig: React.FC = props => ( - + ); public readonly CollectConfig = reactToUiComponent(this.ReactCollectConfig); @@ -63,12 +58,10 @@ export class DashboardToDashboardDrilldown config: Config, context: ActionContext ): Promise => { - const { getUrlForApp } = await this.params.getApplicationService(); const dashboardPath = await this.getDestinationUrl(config, context); - // note: extracting hash and using 'kibana' as appId will be redundant, - // when dashboard move to np urls. (urlGenerator generates np url, which is not supported yet) const dashboardHash = dashboardPath.split('#')[1]; - return getUrlForApp('kibana', { + + return this.params.start().core.application.getUrlForApp('kibana', { path: `#${dashboardHash}`, }); }; @@ -77,26 +70,22 @@ export class DashboardToDashboardDrilldown config: Config, context: ActionContext ) => { - const { navigateToApp } = await this.params.getApplicationService(); const dashboardPath = await this.getDestinationUrl(config, context); - // note: extracting hash and using 'kibana' as appId will be redundant, - // when dashboard move to np urls. (urlGenerator generates np url, which is not supported yet) const dashboardHash = dashboardPath.split('#')[1]; - await navigateToApp('kibana', { + + await this.params.start().core.application.navigateToApp('kibana', { path: `#${dashboardHash}`, }); }; - private async getDestinationUrl( + private getDestinationUrl = async ( config: Config, context: ActionContext - ): Promise { - const getUrlGenerator = await this.params.getGetUrlGenerator(); - + ): Promise => { const { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction, - } = await this.params.getDataPluginActions(); + } = this.params.start().plugins.data.actions; const { timeRange: currentTimeRange, query, @@ -153,11 +142,14 @@ export class DashboardToDashboardDrilldown } } - return getUrlGenerator(DASHBOARD_APP_URL_GENERATOR).createUrl({ - dashboardId: config.dashboardId, - query: config.useCurrentFilters ? query : undefined, - timeRange, - filters: [...existingFilters, ...filtersFromEvent], - }); - } + return this.params + .start() + .plugins.share.urlGenerators.getUrlGenerator(DASHBOARD_APP_URL_GENERATOR) + .createUrl({ + dashboardId: config.dashboardId, + query: config.useCurrentFilters ? query : undefined, + timeRange, + filters: [...existingFilters, ...filtersFromEvent], + }); + }; } diff --git a/x-pack/plugins/drilldowns/public/index.ts b/x-pack/plugins/drilldowns/public/index.ts index 17ccd60e17ce4d..f976356822dce7 100644 --- a/x-pack/plugins/drilldowns/public/index.ts +++ b/x-pack/plugins/drilldowns/public/index.ts @@ -16,5 +16,3 @@ export { export function plugin() { return new DrilldownsPlugin(); } - -export { DrilldownDefinition, DrilldownFactoryContext } from './types'; diff --git a/x-pack/plugins/drilldowns/public/plugin.ts b/x-pack/plugins/drilldowns/public/plugin.ts index bbc06847d58424..0108e04df9c99a 100644 --- a/x-pack/plugins/drilldowns/public/plugin.ts +++ b/x-pack/plugins/drilldowns/public/plugin.ts @@ -7,7 +7,6 @@ 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 './services'; import { createFlyoutManageDrilldowns } from './components/connected_flyout_manage_drilldowns'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; @@ -21,21 +20,17 @@ export interface StartDependencies { advancedUiActions: AdvancedUiActionsStart; } -export type SetupContract = DrilldownServiceSetupContract; - // eslint-disable-next-line +export interface SetupContract {} + export interface StartContract { FlyoutManageDrilldowns: ReturnType; } export class DrilldownsPlugin implements Plugin { - private readonly service = new DrilldownService(); - public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { - const setup = this.service.setup(core, plugins); - - return setup; + return {}; } public start(core: CoreStart, plugins: StartDependencies): StartContract { diff --git a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts b/x-pack/plugins/drilldowns/public/services/drilldown_service.ts deleted file mode 100644 index 78a3b2471ee4ab..00000000000000 --- a/x-pack/plugins/drilldowns/public/services/drilldown_service.ts +++ /dev/null @@ -1,85 +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 { CoreSetup } from 'src/core/public'; -import { - AdvancedUiActionsSetup, - AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition, -} from '../../../advanced_ui_actions/public'; -import { DrilldownDefinition, DrilldownFactoryContext } from '../types'; - -export interface DrilldownServiceSetupDeps { - advancedUiActions: AdvancedUiActionsSetup; -} - -export interface DrilldownServiceSetupContract { - /** - * Convenience method to register a drilldown. - */ - registerDrilldown: < - Config extends object = object, - CreationContext extends object = object, - ExecutionContext extends object = object - >( - drilldown: DrilldownDefinition - ) => void; -} - -export class DrilldownService { - setup( - core: CoreSetup, - { advancedUiActions }: DrilldownServiceSetupDeps - ): DrilldownServiceSetupContract { - const registerDrilldown = < - Config extends object = object, - CreationContext extends object = object, - ExecutionContext extends object = object - >({ - id: factoryId, - order, - CollectConfig, - createConfig, - isConfigValid, - getDisplayName, - euiIcon, - execute, - getHref, - }: DrilldownDefinition) => { - const actionFactory: ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - > = { - id: factoryId, - order, - CollectConfig, - createConfig, - isConfigValid, - getDisplayName, - getIconType: () => euiIcon, - isCompatible: async () => true, - create: serializedAction => ({ - id: '', - type: factoryId, - getIconType: () => euiIcon, - getDisplayName: () => serializedAction.name, - execute: async context => await execute(serializedAction.config, context), - getHref: getHref ? async context => getHref(serializedAction.config, context) : undefined, - }), - } as ActionFactoryDefinition< - Config, - DrilldownFactoryContext, - ExecutionContext - >; - - advancedUiActions.registerActionFactory(actionFactory); - }; - - return { - registerDrilldown, - }; - } -} From 6755e47926fb60c35961a836236c96afaf218d49 Mon Sep 17 00:00:00 2001 From: streamich Date: Wed, 29 Apr 2020 17:14:15 +0200 Subject: [PATCH 109/129] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20functio?= =?UTF-8?q?nal=20test=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/drilldowns/data.json.gz | Bin 2640 -> 2662 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz b/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz index febf312799eee0de78a6de2226c17d4fe6183c68..a9b23ca7a579bdd2de633c67e386a31313162e2c 100644 GIT binary patch delta 2387 zcmV-Z39RyRt6ekO4 zxK}@I#uF0FN)-82b~6kgEWTiSP6F3Ki3?3UN%3+CZ61FKI8K0ab+c>~94$-ybHW3u zp6!Y^whEn+F-LqQ%Cg*eixZY<%qmj}lg}v8<*-tSb38XHV@sig(PT3hV@pzu5)`sb z2Qf}A(M;T7nb5?1I=@5-Cny-vxwywe0mG^VEJ7T1$Pa_y4Bz6QXcrnnvzfTQ66SE_ zh*p&Who*lKmn7!+I7E^dVlara#ujLN)l0kD3q%~JGq`iTr)lbZ#(YCSkKiEdXu9B- z$#xQfEg^(}+%`=iH@@Q-e8{z@z@)2~Sl&QO0OR2ZERoTQ;QdZf+Wde)`wj};qU6X2 zOK1$!2UvO8(P}&<^$x?e>gQ#TX22Ex>F|y)C9+abhU-o!4LFG_v@<*dw_cGL$dz>; zkO+(;`wpp_(ZCcCLT<(f39$_48`V2pzemYusnr}4RLcA5L_`m-patGWT_~pSmpcRu z7nE;5h#CyfLX)@xW&!S#9|J8W%yR*7B+44M1Ttxo#h=f4%!Ye=Q^X+H<5nc~f&%e- zMsN4PgJzR*11*2F$YS@pkr6z>K9>X%LY=i23$GKVNl*mkej>4_P$&ntoTfbQ7&u`@ zoX}eWU|~!bhTt(nFun+IHqdorxl1^mVd_I**|UVW-wvkmbjzVMaS511Rbv7~FB9w# zrBJ1exI}7yCv=p6(TqXwIEjWU(D-&ptn^gHJSjaBy$FBXh?haBiUfN&QLa^JH^-&d zg}H{ZESI2JadSCfB^oo>D&HgWj+8sE6r^1X4!|Fkv7UK+MX3z0-V>@C2n3)U(MS{u zEjk;@7#Yrq%l%sMmGPmCp*zM}37Ttk6@X$0*_m!OHZ4;sC^Q-fP!zRNNF}WRF`kjj z%tUE8RiS^rb0vwlc}VC(j%J#zC%_-<^<5z6m+6Mji2&>jcj|*Uxycxmzu?%K&6aY< z7VkituT^@DmeuBZW8U$R&J6AZ1T;e8Dhujb2OMV!+{a|*>#Qt%`h8I5sjh(>pG3Ho z&o}7F0|ALXGmTmxu14YCK)Tait;BvD;*g5Tg}Q$f17+;gtev`{R{hd{NL>OikFMVA zt0vXzq8vHBQx|5ycNpQ=7VX+!jedqEuAPgA-aRxLP0n*@R*YQQ*O`qUNCd)tYcjW> zQ8{)tp;zUgOnfp6Ipu3=c=~jh_K23aqO*<^U1-*VT(M)iCz&+YlJ+x|0?!vCWfZF) zw$^`RZgLY=^p;TDmVnBuOLk0&T9(*(y#kui3^a09NpV#lr^!89E;wsXNE@p&wcv`y zG-A5hcoSmA^cM~J1Q@MR#k)4CULVFXMAmGvJEqfAa;j2$1aHL^&6$!Vj^-sZ{k~Zg z-?c$C-6$_6w>2-X)Gu0rj~Xm+L%?dEW~zUkZOY4nx>vL7G=IG1O|f>iD<-(>r#9UQ zITC8^sQWV&`DvO*l5O9n)#sDX+Cg7!VL5-7jnipB zn6G<@*k1qCW5=_?_Vaw7`)fF04o&c{N1AVA;igg9coO0t#HZ%xYP<0Ye;eEOr<8xb zc7Jd~a`mhB@3IN$l3?BY9M@QQ$=i^&iMqpmNHNJ`L%6tGp|9R{h`8vLA5Dozf_HWikhWg(^97owIo z2+M$oX&_6NVP%){)~(AfRAsV{x0rv=F^(?9(qUR+0HG`%?wgEqJ zKpq31&8bvIzKH(BeoLCTQVrKK24$-xy-CHHzVWdEkM$ekNx2EZ>x%iRX{o4s6FZhHJ7ou=VA&iFY zuGjGf`)0E@a0gAx!l;R?uG_?&uGMP~P&EA-?P(yQ&XNZxjgm6q{WFst2rhrMI&4G0s(1lisQ*b;FZMcpci%Ic zsE>QiZm+Z7Jg~6a>O|yd!oPNLCo=&|i zh7*4>n`Oapb=~-Ic=qFoeROtu^e1~VI(_%P3+O(`MZ-bupW&d*$H^dQJRR;L%^nZ1b&EDZnJ9q zK|11U(iSuW97Ca^#ydV7|KaV$;po_&T-blBi{sJh+1c^M$8&ME$W3;~x2g z*=i5^pVlWlq4WcBimt1T6I$-7-IRe{e#<&A*Sb@sV&}rgLK>pO=49 z_QuR#1h2P_Qoa3B&C(U1g}04+O_KP!K`7&nGS!=+B+wIJM} zAQhB*xPVLIidGgQ2M=BBp)}y~KG77p_*S&GK9d=WP)=twO`KI1hI+h20FsNDlaQDb z8i_6@K4Av@<32XjFot~UcdA$UP=rFs?6G1*wJh^n3Td(dC3X2q{4HO-x_`R3H-x1q zAYYJRZ}_2n^{;=w{^LLY{M*0({+H%4;%I_q0seM<I9^^gQ=woz) z-HX|uvBTeEjw;;*pd{Z1cfZq zL5!12G#7VRCNwdhE-q2R2?|DZA@1=|z_4loix7t$^1~oF!#6l6+J%PDd@io9ggIO} zqE%(Up=p1_C5bsc4v{2=7!2a9u>~4m_0q2P0ujgQ9PV82X_h*lG2al-BRI%9n%!~C zWIKt#mJmWfZkwi%8{cvaKIGanVA54gEU%#@fbnnymdI#D@P4N#ZGOO@eFp_^QF7#i zB{YWV1FXF4Xf+;_dWYd!_4BevGvEsUba+dc5?N9x!*wT=2Asqd+8LgMTdznAK9>X%LY=i23$GKVNl*mkej>4_P$&ntoTWVP7&u`@ zoX{HrU}4Pe48dcDVEit?*+A3Aa+h#A$JB?wvgZkLza32B>6Sxj;u0{2s>TF}UMAQf zN});_af#IaPUt8BqZxzVaS{zzpz-aHSm~*Xc~W{NdJ%uN5if&M6$$omqFk%cZjMW@ z3v&%+SuR1d;^uO|N;GD$RlY~$9VvHSDM-5(9DqM6V?Fcuic%R~y(d&P5C}jyqLC;R zT68{?F)~~bm;1HiE8{~OL${2z6133hDgeb0vUA;RY+9yNP-rv|peSmkkV;wsVmu?4 znTgVHrb2&x=SmW9^N`So9L+UbPk=wz>$^bCFVhWQ5CPa3?$ifya+5JAf5EXen=R#z zE#84PU#s*QEvwD-#=PSrof+H-2xx@FRTk8>4mi#dxR1%)*I8Nk^!uR9Q(Xf&K8bKE zpKs8U2Lcj(W*W6XT#drNfpn+4T8aHQ#32=v3w3`f2Flo}Svz$@t@@?^kh%n39$mfJ zS52zbMLBYMr!LHZ?=Zr#E!wre8vP7STss#Jy?bahnw;m*tQfhpuQMAzkO+kP)?{u$ zqjKzQLa)j}nfPQDa?01#@bu|0?GY_;MQ0r;y3nizxnjq3Pcmt)CGBS_1)eWP$|zPp zY^{IC+~g*#=q;hPEdiBRm+Y7lwJfpodIdD28EE9JlH#g9PLq4ITyWN&kTzClYQYtY zX~cB1@g~HK=`R}c2{2ltig#^Ny*`X(h^*OSw@jz0h2? zFkkl)vAzDO$Bt)(?dSPE_t$X19Gc)?k2K%L!gZsv@g&4Qh)>PW)pp|*{x-JlPbq(W z?f&3~$TMwcBEI3tnv;9Te71xKekC}69^I}B2>HTXHBRGb`;Z7M81%R)-IFGMYG z5S9TE(?FIk!^$q@ty`B}sLEs?Z!v#gU>seFrNh!FiQHc}5<&uT@cJd~YX(h)Yy*Dc zfIJ31n^UQbd=dSL{gyOwr5dI(24$-xy}c_*kQiZm+Z7Jg~6a>^wR8F1FwlZJZqXAeBku^-S%weWzm_C`8;#;t7+rI;n|NT_R-nt z(Vu_p$>{XmyOWW;YkYEcax}3=hv)B3Rt*x7)K@~o@-*K&YH(`A1RG-GCpQY;RI_|5 z0g4vIch|!G{=j-PHe@H@2S;fl&IJK$;SqQkUOB|7@dxQ*tLa$K3~&r3h#K$saQuh2 z7l)%`dval~E{;d1XJ^M3AJ4_9A=ic7M%aHlFLtd7f(=enonB0^mtf}S03>2ZI3I8X zKP7uBd@$D%W{7`CXej)%ZCd+H6BIC*Sp8wAJG9JJd(i*1K9LC}9SBo&U2UAua#!un z#59NIU}*JQ)`7X!mCDt}xjBrFg!?j08yk7Ll!`ZIM6=U6A_og(xhkHi;75jsPbq)K z)C2j!kV@s%#_d(u3Qg3n>y1g<6<`)kX)JP-Ya^a%u5CiVNF!Lo{lRd*)9Rbnr(Zt7 zyUGDi@$=tufE?<7B?lZh`>xlUwVS5n44U18gMJgW!MHs%K-Qq`b-HGMYYuqYgAe3@ j|3_?E2yD89?6M1JT3>llch(z|Ui}Fd=^*Z`{#O71vx$Zu From 356b240f3b82d8d8087b40168a0cc864691a24ad Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Thu, 30 Apr 2020 11:06:28 +0200 Subject: [PATCH 110/129] merge --- .../drilldown.test.tsx | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) 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 index 2f2417e599bcfd..18ee95cb57b3bc 100644 --- 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 @@ -25,6 +25,9 @@ import { RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../../../src/plugins/embeddable/public'; +import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; +import { StartServicesGetter } from '../../../../../../../src/plugins/kibana_utils/public/core'; +import { StartDependencies } from '../../../plugin'; describe('.isConfigValid()', () => { const drilldown = new DashboardToDashboardDrilldown({} as any); @@ -87,33 +90,38 @@ describe('.execute() & getHref', () => { const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; const drilldown = new DashboardToDashboardDrilldown({ - start: () => - ({ - core: { - application: { - navigateToApp, - getUrlForApp, - }, - savedObjects: { - client: savedObjectsClient, - }, + start: ((() => ({ + core: { + application: { + navigateToApp, + getUrlForApp, }, - plugins: { - advancedUiActions: {}, - data: { - actions: dataPluginActions, - }, - share: { - urlGenerators: { - getUrlGenerator: () => - createDirectAccessDashboardLinkGenerator(() => - Promise.resolve({ appBasePath: 'test', useHashedUrl: false }) - ) as UrlGeneratorContract, - }, + savedObjects: { + client: savedObjectsClient, + }, + }, + plugins: { + advancedUiActions: {}, + data: { + actions: dataPluginActions, + }, + share: { + urlGenerators: { + getUrlGenerator: () => + createDirectAccessDashboardLinkGenerator(() => + Promise.resolve({ + appBasePath: 'test', + useHashedUrl: false, + savedDashboardLoader: ({} as unknown) as SavedObjectLoader, + }) + ) as UrlGeneratorContract, }, }, - self: {}, - } as any), + }, + self: {}, + })) as unknown) as StartServicesGetter< + Pick + >, }); const selectRangeFiltersSpy = jest .spyOn(dataPluginActions, 'createFiltersFromRangeSelectAction') From bb1415978730eff50f8457e121c3620aae72397f Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 10:22:11 +0200 Subject: [PATCH 111/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20make=20dri?= =?UTF-8?q?lldown=20enhancement=20comment=20more=20general?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts | 2 +- 1 file changed, 1 insertion(+), 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 90f8d2e0098436..c16698a5f8637b 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -36,7 +36,7 @@ export interface EmbeddableInput { hidePanelTitles?: boolean; /** - * Reserved key for enhancements added by X-Pack plugins. + * Reserved key for enhancements added by other plugins. */ enhancements?: unknown; From 60a433ae580585d151092773ae02fe66a48bd8b5 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 10:25:21 +0200 Subject: [PATCH 112/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20return=20public?= =?UTF-8?q?=20type=20from=20registerAction()=20call?= 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 | 2 +- 1 file changed, 1 insertion(+), 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 8d63fdcec90073..61de4917803ed3 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -77,7 +77,7 @@ export class UiActionsService { public readonly registerAction =
( definition: A - ): ActionInternal => { + ): Action> => { if (this.actions.has(definition.id)) { throw new Error(`Action [action.id = ${definition.id}] already registered.`); } From 9309e79c0326d5e842d699a76a38c04fe4fe73ea Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 10:26:14 +0200 Subject: [PATCH 113/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20comm?= =?UTF-8?q?ent=20to=20value=20click=20trigger=20title=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/ui_actions/public/triggers/value_click_trigger.ts | 2 ++ 1 file changed, 2 insertions(+) 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 2671584d105c87..5fe060f55dc773 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,8 @@ import { Trigger } from '.'; export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { id: VALUE_CLICK_TRIGGER, + // This is empty string to hide title of ui_actions context menu that appears + // when this trigger is executed. title: '', description: 'Value was clicked', }; From 930cdba3f140db8f68d6416065c0200ed0acfc8a Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 10:47:37 +0200 Subject: [PATCH 114/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20improve=20?= =?UTF-8?q?comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/collect_config_container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index 08fd35184b396e..494f3f543fed61 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -126,7 +126,7 @@ export class CollectConfigContainer extends React.Component< if (!this.isMounted) return; - // handle case when destination dashboard is no longer exist + // handle case when destination dashboard no longer exists if (savedObject.error?.statusCode === 404) { this.setState({ error: txtDestinationDashboardNotFound(config.dashboardId), From 5c885df4d8d2e338d1795a364ccda3cc6bdd97eb Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 12:05:56 +0200 Subject: [PATCH 115/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20use=20second=20ar?= =?UTF-8?q?gument=20of=20CollectConfigProps=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/collect_config_container.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index 494f3f543fed61..64f3b719da503a 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -13,7 +13,7 @@ import { txtDestinationDashboardNotFound } from './i18n'; import { CollectConfigProps } from '../../../../../../../../src/plugins/kibana_utils/public'; import { UiActionsEnhancedDrilldownActionFactoryContext as DrilldownFactoryContext } from '../../../../../../advanced_ui_actions/public'; import { Config } from '../types'; -import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public'; +import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; import { Params } from '../drilldown'; const mergeDashboards = ( @@ -36,11 +36,9 @@ const dashboardSavedObjectToMenuItem = ( label: savedObject.attributes.title, }); -interface DashboardDrilldownCollectConfigProps extends CollectConfigProps { +interface DashboardDrilldownCollectConfigProps + extends CollectConfigProps> { params: Params; - context: DrilldownFactoryContext<{ - embeddable: IEmbeddable; - }>; } interface CollectConfigContainerState { From a6b57fb5f7f8f5663f45cbe2fc5dd3dd7631088b Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 12:19:29 +0200 Subject: [PATCH 116/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20add=20workaround?= =?UTF-8?q?=20for=20Firefox=20rendering=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See: https://github.com/elastic/kibana/pull/61219/#pullrequestreview-402903330 --- .../public/components/action_wizard/action_wizard.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 9c2e34d157d705..867ead688d23d0 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 @@ -169,8 +169,14 @@ const ActionFactorySelector: React.FC = ({ return
No action factories to pick from
; } + // The below style is applied to fix Firefox rendering bug. + // See: https://github.com/elastic/kibana/pull/61219/#pullrequestreview-402903330 + const firefoxBugFix = { + willChange: 'opacity', + }; + return ( - + {[...actionFactories] .sort((f1, f2) => f2.order - f1.order) .map(actionFactory => ( From 00b9e02319ef0aed5045079bc59c4b359fcc9a9c Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 12:25:08 +0200 Subject: [PATCH 117/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20delete=20unused?= =?UTF-8?q?=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/drilldowns/public/types.ts | 131 ---------------------- 1 file changed, 131 deletions(-) delete mode 100644 x-pack/plugins/drilldowns/public/types.ts diff --git a/x-pack/plugins/drilldowns/public/types.ts b/x-pack/plugins/drilldowns/public/types.ts deleted file mode 100644 index ec282f611af720..00000000000000 --- a/x-pack/plugins/drilldowns/public/types.ts +++ /dev/null @@ -1,131 +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 { AdvancedUiActionsActionFactoryDefinition as ActionFactoryDefinition } from '../../advanced_ui_actions/public'; - -/** - * 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, - PlaceContext extends object = object, - ExecutionContext extends object = object -> { - /** - * Globally unique identifier for this drilldown. - */ - id: string; - - /** - * Determines the display order of the drilldowns in the flyout picker. - * Higher numbers are displayed first. - */ - order?: number; - - /** - * 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 { CollectConfigProps } from 'src/plugins/kibana_utils/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; - - /** - * Should return an internationalized name of the drilldown, which will be - * displayed to the user. - */ - getDisplayName: () => string; - - /** - * 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 - * `UIAction` of this drilldown is being executed in. - */ - execute(config: Config, context: ExecutionContext): void; - - /** - * A link where drilldown should navigate on middle click or Ctrl + click. - */ - getHref?(config: Config, context: ExecutionContext): Promise; -} - -/** - * Context object used when creating a drilldown. - */ -export interface DrilldownFactoryContext { - /** - * 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 71f2d23d095702fe70e80f748d984664b441a4f9 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 12:36:33 +0200 Subject: [PATCH 118/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20import=20type=20f?= =?UTF-8?q?rom=20new=20location?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connected_flyout_manage_drilldowns.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f049b231c15cae..75d66b2af8db5c 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 { UiActionsEnhancedDynamicActionManager as DynamicActionManager, UiActionsEnhancedSerializedAction, UiActionsEnhancedSerializedEvent, + UiActionsEnhancedDrilldownActionFactoryContext as DrilldownFactoryContext, } from '../../../../advanced_ui_actions/public'; import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; @@ -31,7 +32,6 @@ import { toastDrilldownsCRUDError, toastDrilldownsDeleted, } from './i18n'; -import { DrilldownFactoryContext } from '../../types'; interface ConnectedFlyoutManageDrilldownsProps { placeContext: Context; From e91059aaaf4e38f25a237578502bc775dd2d8b37 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 13:10:12 +0200 Subject: [PATCH 119/129] =?UTF-8?q?style:=20=F0=9F=92=84=20make=20generic?= =?UTF-8?q?=20type=20variable=20name=20sconsistent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui_actions/public/service/ui_actions_service.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 61de4917803ed3..9a08aeabb00f3b 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -97,10 +97,7 @@ export class UiActionsService { this.actions.delete(actionId); }; - public readonly attachAction = ( - triggerId: TriggerId, - actionId: string - ): void => { + public readonly attachAction = (triggerId: T, actionId: string): void => { const trigger = this.triggers.get(triggerId); if (!trigger) { @@ -139,11 +136,11 @@ export class UiActionsService { * * `addTriggerAction` also infers better typing of the `action` argument. */ - public readonly addTriggerAction = ( - triggerId: TType, + public readonly addTriggerAction = ( + triggerId: T, // 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: Action + action: Action ): void => { if (!this.actions.has(action.id)) this.registerAction(action); this.attachAction(triggerId, action.id); From 17ebb52e8b5a01a50fb1fe31b5a1c2790e9e73a2 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 15:14:51 +0200 Subject: [PATCH 120/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20show=20"Create=20?= =?UTF-8?q?drilldown"=20only=20on=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flyout_create_drilldown/flyout_create_drilldown.tsx | 1 + .../public/services/drilldowns/actions/test_helpers.ts | 5 +++++ 2 files changed, 6 insertions(+) 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 34b84de61bb644..fe1d1fe92f9201 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 @@ -40,6 +40,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; } 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 index 03000b4fc04f3e..e50617e2b1e6f2 100644 --- 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 @@ -29,6 +29,11 @@ export class MockEmbeddable extends Embeddable { public supportedTriggers(): Array { return this.triggers; } + public getRoot() { + return { + type: 'dashboard', + } as Embeddable; + } } export const enhanceEmbeddable = ( From d72961b1b3d4c742310d4ff40a035c0e7e7498f9 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 30 Apr 2020 15:21:52 +0200 Subject: [PATCH 121/129] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20extra=20un?= =?UTF-8?q?it=20test=20for=20root=20embeddable=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flyout_create_drilldown.test.tsx | 10 ++++++++++ .../public/services/drilldowns/actions/test_helpers.ts | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) 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 ee25027959821e..5ec1b881317d69 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 @@ -49,6 +49,7 @@ interface CompatibilityParams { isEdit?: boolean; isValueClickTriggerSupported?: boolean; isEmbeddableEnhanced?: boolean; + rootType?: string; } describe('isCompatible', () => { @@ -59,6 +60,7 @@ describe('isCompatible', () => { isEdit = true, isValueClickTriggerSupported = true, isEmbeddableEnhanced = true, + rootType = 'dashboard', }: CompatibilityParams, expectedResult: boolean = true ): Promise { @@ -71,6 +73,8 @@ describe('isCompatible', () => { } ); + embeddable.rootType = rootType; + if (isEmbeddableEnhanced) { embeddable = enhanceEmbeddable(embeddable); } @@ -106,6 +110,12 @@ describe('isCompatible', () => { isEdit: false, }); }); + + test('not compatible if root embeddable is not "dashboard"', async () => { + await assertNonCompatibility({ + rootType: 'visualization', + }); + }); }); describe('execute', () => { 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 index e50617e2b1e6f2..cccacf701a9ada 100644 --- 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 @@ -15,6 +15,7 @@ import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actio import { uiActionsEnhancedPluginMock } from '../../../../../advanced_ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { + public rootType = 'dashboard'; public readonly type = 'mock'; private readonly triggers: Array = []; constructor( @@ -31,7 +32,7 @@ export class MockEmbeddable extends Embeddable { } public getRoot() { return { - type: 'dashboard', + type: this.rootType, } as Embeddable; } } From fc652d2b5da618e78397963c7feda50429caf4fc Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 08:57:48 +0200 Subject: [PATCH 122/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20g?= =?UTF-8?q?enerated=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/data/public/public.api.md | 93 ++++++++++++++------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e36e9427249351..f15bf420b556c1 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1784,52 +1784,53 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/common/es_query/filters/match_all_filter.ts:28:3 - (ae-forgotten-export) The symbol "MatchAllFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:65:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:135:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:375:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:375:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:375:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:375:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:378:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "generateFilters" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "changeTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "convertRangeFilterToTimeRangeString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "extractTimeFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:137:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:179:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:379:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts From 79bc7425ab365ddcbcbed366cb8c19722b4ce85f Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 09:41:56 +0200 Subject: [PATCH 123/129] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20example?= =?UTF-8?q?=20warnings=20to=20sample=20drilldowns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discover_drilldown_config.tsx | 10 +++++++++- .../public/dashboard_to_url_drilldown/index.tsx | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx index be54d8b2982e17..67b4a56dd55e7c 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { EuiFormRow, EuiSelect, EuiSwitch, EuiSpacer } from '@elastic/eui'; +import { EuiFormRow, EuiSelect, EuiSwitch, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { txtChooseDestinationIndexPattern } from './i18n'; export interface IndexPatternItem { @@ -38,6 +38,14 @@ export const DiscoverDrilldownConfig: React.FC = ( }) => { return ( <> + +

+ This is an example drilldown. It is meant as a starting point for developers, so they can + grab this code and get started. It does not provide a complete working functionality but + serves as a getting started example. +

+
+ {!!onCustomIndexPatternToggle && ( <> diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index ec968807f5720b..bd3dc83d2707e2 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { EuiFormRow, EuiSwitch, EuiFieldText } from '@elastic/eui'; +import { EuiFormRow, EuiSwitch, EuiFieldText, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { @@ -38,6 +38,14 @@ export class DashboardToUrlDrilldown implements Drilldown = ({ config, onConfig }) => ( <> + +

+ This is an example drilldown. It is meant as a starting point for developers, so they can + grab this code and get started. It does not provide a complete working functionality but + serves as a getting started example. +

+
+ Date: Mon, 4 May 2020 09:51:22 +0200 Subject: [PATCH 124/129] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20link?= =?UTF-8?q?s=20to=20example=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discover_drilldown_config/discover_drilldown_config.tsx | 4 ++++ .../public/dashboard_to_url_drilldown/index.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx index 67b4a56dd55e7c..cf379b29a00390 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/components/discover_drilldown_config/discover_drilldown_config.tsx @@ -44,6 +44,10 @@ export const DiscoverDrilldownConfig: React.FC = ( grab this code and get started. It does not provide a complete working functionality but serves as a getting started example.

+

+ Implementation of the actual Go to Discover drilldown is tracked in{' '} + #60227 +

{!!onCustomIndexPatternToggle && ( diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index bd3dc83d2707e2..d7d8a51f196091 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -44,6 +44,10 @@ export class DashboardToUrlDrilldown implements Drilldown +

+ Implementation of the actual Go to URL drilldown is tracked in{' '} + #55324 +

From 11a25e7e0dc19c8244829c82e861d61f22786034 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 10:16:11 +0200 Subject: [PATCH 125/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20URL=20dril?= =?UTF-8?q?ldown=20validation=20and=20https://=20prefixing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard_to_url_drilldown/index.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index d7d8a51f196091..71aae253dcec29 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -15,6 +15,15 @@ import { } from '../../../../../src/plugins/embeddable/public'; import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public'; +function isValidUrl(url: string) { + try { + new URL(url); + return true; + } catch { + return false; + } +} + export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; @@ -53,10 +62,15 @@ export class DashboardToUrlDrilldown implements Drilldown onConfig({ ...config, url: event.target.value })} + onBlur={() => { + if (!config.url) return; + if (/https?:\/\//.test(config.url)) return; + onConfig({ ...config, url: 'https://' + config.url }); + }} /> @@ -78,7 +92,8 @@ export class DashboardToUrlDrilldown implements Drilldown { - return !!config.url && typeof config.url === 'string'; + if (!config.url) return false; + return isValidUrl(config.url); }; /** From 7221466f4ceaa0e7036877b9925e690e4218671a Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 11:07:00 +0200 Subject: [PATCH 126/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20disable=20drilldo?= =?UTF-8?q?wns=20for=20lens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flyout_create_drilldown/flyout_create_drilldown.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) 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 fe1d1fe92f9201..81f88e563a2586 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 @@ -41,6 +41,15 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; } From 2dacae31595c101774cd3bd5a2abefa8714e2f65 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 11:34:59 +0200 Subject: [PATCH 127/129] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20Pla?= =?UTF-8?q?ceContext=20from=20DrilldownDefinition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard_hello_world_drilldown/index.tsx | 4 +--- .../drilldown.tsx | 5 ++-- .../dashboard_to_discover_drilldown/index.ts | 1 - .../dashboard_to_discover_drilldown/types.ts | 2 -- .../dashboard_to_url_drilldown/index.tsx | 4 +--- .../drilldown_action_factory_context.ts | 23 ------------------- .../public/drilldowns/drilldown_definition.ts | 20 +++------------- .../public/drilldowns/index.ts | 1 - .../advanced_ui_actions/public/index.ts | 5 +--- .../ui_actions_service_enhancements.ts | 17 ++++---------- .../components/collect_config_container.tsx | 10 ++------ .../drilldown.tsx | 21 ++++++++--------- .../dashboard_to_dashboard_drilldown/index.ts | 1 - .../dashboard_to_dashboard_drilldown/types.ts | 2 -- .../connected_flyout_manage_drilldowns.tsx | 1 - 15 files changed, 24 insertions(+), 93 deletions(-) delete mode 100644 x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx index 75e85d0e302625..b1e1040daee6ea 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_hello_world_drilldown/index.tsx @@ -9,7 +9,6 @@ import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { - EmbeddableContext, RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../src/plugins/embeddable/public'; @@ -23,8 +22,7 @@ export interface Config { const SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN = 'SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN'; -export class DashboardHelloWorldDrilldown - implements Drilldown { +export class DashboardHelloWorldDrilldown implements Drilldown { public readonly id = SAMPLE_DASHBOARD_HELLO_WORLD_DRILLDOWN; public readonly order = 6; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx index 7ab56461507446..1213ec2f35995e 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/drilldown.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { StartDependencies as Start } from '../plugin'; import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; import { StartServicesGetter } from '../../../../../src/plugins/kibana_utils/public'; -import { PlaceContext, ActionContext, Config, CollectConfigProps } from './types'; +import { ActionContext, Config, CollectConfigProps } from './types'; import { CollectConfigContainer } from './collect_config_container'; import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; @@ -25,8 +25,7 @@ export interface Params { start: StartServicesGetter>; } -export class DashboardToDiscoverDrilldown - implements Drilldown { +export class DashboardToDiscoverDrilldown implements Drilldown { constructor(protected readonly params: Params) {} public readonly id = SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts index 7016c5147ef87e..e824c49a6f1fa5 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/index.ts @@ -10,7 +10,6 @@ export { Params as DashboardToDiscoverDrilldownParams, } from './drilldown'; export { - PlaceContext as DashboardToDiscoverPlaceContext, ActionContext as DashboardToDiscoverActionContext, Config as DashboardToDiscoverConfig, } from './types'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts index f7bb3885f6f62c..5dfc250a56d288 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_discover_drilldown/types.ts @@ -7,11 +7,9 @@ import { RangeSelectTriggerContext, ValueClickTriggerContext, - EmbeddableContext, } from '../../../../../src/plugins/embeddable/public'; import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../src/plugins/kibana_utils/public'; -export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; export interface Config { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx index 71aae253dcec29..cc38386b26385e 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/dashboard_to_url_drilldown/index.tsx @@ -9,7 +9,6 @@ import { EuiFormRow, EuiSwitch, EuiFieldText, EuiCallOut, EuiSpacer } from '@ela import { reactToUiComponent } from '../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../plugins/advanced_ui_actions/public'; import { - EmbeddableContext, RangeSelectTriggerContext, ValueClickTriggerContext, } from '../../../../../src/plugins/embeddable/public'; @@ -24,7 +23,6 @@ function isValidUrl(url: string) { } } -export type PlaceContext = EmbeddableContext; export type ActionContext = RangeSelectTriggerContext | ValueClickTriggerContext; export interface Config { @@ -36,7 +34,7 @@ export type CollectConfigProps = CollectConfigPropsBase; const SAMPLE_DASHBOARD_TO_URL_DRILLDOWN = 'SAMPLE_DASHBOARD_TO_URL_DRILLDOWN'; -export class DashboardToUrlDrilldown implements Drilldown { +export class DashboardToUrlDrilldown implements Drilldown { public readonly id = SAMPLE_DASHBOARD_TO_URL_DRILLDOWN; public readonly order = 8; diff --git a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts deleted file mode 100644 index b1a70948c9450b..00000000000000 --- a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_action_factory_context.ts +++ /dev/null @@ -1,23 +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. - */ - -/** - * Context object used when creating a drilldown. - */ -export interface DrilldownActionFactoryContext { - /** - * 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[]; -} diff --git a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts index 21f7fc0e7c47c0..16c8077d727cbd 100644 --- a/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts +++ b/x-pack/plugins/advanced_ui_actions/public/drilldowns/drilldown_definition.ts @@ -5,7 +5,6 @@ */ import { ActionFactoryDefinition } from '../dynamic_actions'; -import { DrilldownActionFactoryContext } from './drilldown_action_factory_context'; /** * This is a convenience interface to register a drilldown. Drilldown has @@ -27,7 +26,6 @@ import { DrilldownActionFactoryContext } from './drilldown_action_factory_contex */ export interface DrilldownDefinition< Config extends object = object, - PlaceContext extends object = object, ExecutionContext extends object = object > { /** @@ -44,11 +42,7 @@ export interface DrilldownDefinition< /** * Function that returns default config for this drilldown. */ - createConfig: ActionFactoryDefinition< - Config, - DrilldownActionFactoryContext, - ExecutionContext - >['createConfig']; + createConfig: ActionFactoryDefinition['createConfig']; /** * `UiComponent` that collections config for this drilldown. You can create @@ -69,21 +63,13 @@ export interface DrilldownDefinition< * export const CollectConfig = uiToReactComponent(ReactCollectConfig); * ``` */ - CollectConfig: ActionFactoryDefinition< - Config, - DrilldownActionFactoryContext, - ExecutionContext - >['CollectConfig']; + CollectConfig: ActionFactoryDefinition['CollectConfig']; /** * A validator function for the config object. Should always return a boolean * given any input. */ - isConfigValid: ActionFactoryDefinition< - Config, - DrilldownActionFactoryContext, - ExecutionContext - >['isConfigValid']; + isConfigValid: ActionFactoryDefinition['isConfigValid']; /** * Name of EUI icon to display when showing this drilldown to user. diff --git a/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts b/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts index 3950e9b46ba336..7f81a68c803eba 100644 --- a/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/drilldowns/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './drilldown_action_factory_context'; export * from './drilldown_definition'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index e7bc7c50bd6219..024cfe5530b971 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -30,7 +30,4 @@ export { MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, } from './dynamic_actions'; -export { - DrilldownDefinition as UiActionsEnhancedDrilldownDefinition, - DrilldownActionFactoryContext as UiActionsEnhancedDrilldownActionFactoryContext, -} from './drilldowns'; +export { DrilldownDefinition as UiActionsEnhancedDrilldownDefinition } from './drilldowns'; diff --git a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts index 07e00e4a24d151..8befbf43d3c6a2 100644 --- a/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/advanced_ui_actions/public/services/ui_actions_service_enhancements.ts @@ -6,7 +6,7 @@ import { ActionFactoryRegistry } from '../types'; import { ActionFactory, ActionFactoryDefinition } from '../dynamic_actions'; -import { DrilldownDefinition, DrilldownActionFactoryContext } from '../drilldowns'; +import { DrilldownDefinition } from '../drilldowns'; export interface UiActionsServiceEnhancementsParams { readonly actionFactories?: ActionFactoryRegistry; @@ -61,7 +61,6 @@ export class UiActionsServiceEnhancements { */ public readonly registerDrilldown = < Config extends object = object, - PlaceContext extends object = object, ExecutionContext extends object = object >({ id: factoryId, @@ -73,12 +72,8 @@ export class UiActionsServiceEnhancements { euiIcon, execute, getHref, - }: DrilldownDefinition): void => { - const actionFactory: ActionFactoryDefinition< - Config, - DrilldownActionFactoryContext, - ExecutionContext - > = { + }: DrilldownDefinition): void => { + const actionFactory: ActionFactoryDefinition = { id: factoryId, order, CollectConfig, @@ -95,11 +90,7 @@ export class UiActionsServiceEnhancements { execute: async context => await execute(serializedAction.config, context), getHref: getHref ? async context => getHref(serializedAction.config, context) : undefined, }), - } as ActionFactoryDefinition< - Config, - DrilldownActionFactoryContext, - ExecutionContext - >; + } as ActionFactoryDefinition; this.registerActionFactory(actionFactory); }; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx index 64f3b719da503a..dc19fccf5c92ff 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_to_dashboard_drilldown/components/collect_config_container.tsx @@ -11,9 +11,7 @@ import { SimpleSavedObject } from '../../../../../../../../src/core/public'; import { DashboardDrilldownConfig } from './dashboard_drilldown_config'; import { txtDestinationDashboardNotFound } from './i18n'; import { CollectConfigProps } from '../../../../../../../../src/plugins/kibana_utils/public'; -import { UiActionsEnhancedDrilldownActionFactoryContext as DrilldownFactoryContext } from '../../../../../../advanced_ui_actions/public'; import { Config } from '../types'; -import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; import { Params } from '../drilldown'; const mergeDashboards = ( @@ -36,8 +34,7 @@ const dashboardSavedObjectToMenuItem = ( label: savedObject.attributes.title, }); -interface DashboardDrilldownCollectConfigProps - extends CollectConfigProps> { +interface DashboardDrilldownCollectConfigProps extends CollectConfigProps { params: Params; } @@ -146,7 +143,6 @@ export class CollectConfigContainer extends React.Component< private readonly debouncedLoadDashboards: (searchString?: string) => void; private async loadDashboards(searchString?: string) { - const currentDashboardId = this.props.context.placeContext.embeddable?.parent?.id; this.setState({ searchString, isLoading: true }); const savedObjectsClient = this.props.params.start().core.savedObjects.client; const { savedObjects } = await savedObjectsClient.find<{ title: string }>({ @@ -161,9 +157,7 @@ export class CollectConfigContainer extends React.Component< if (!this.isMounted) return; if (searchString !== this.state.searchString) return; - const dashboardList = savedObjects - .map(dashboardSavedObjectToMenuItem) - .filter(({ value }) => !currentDashboardId || value !== currentDashboardId); + const dashboardList = savedObjects.map(dashboardSavedObjectToMenuItem); this.setState({ dashboards: dashboardList, isLoading: false }); } 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 eebea9ffb2c07c..848e77384f7f04 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 { reactToUiComponent } from '../../../../../../../src/plugins/kibana_react/public'; import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../src/plugins/dashboard/public'; -import { PlaceContext, ActionContext, Config } from './types'; +import { ActionContext, Config } from './types'; import { CollectConfigContainer } from './components'; import { DASHBOARD_TO_DASHBOARD_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../advanced_ui_actions/public'; @@ -26,7 +26,7 @@ export interface Params { } export class DashboardToDashboardDrilldown - implements Drilldown> { + implements Drilldown> { constructor(protected readonly params: Params) {} public readonly id = DASHBOARD_TO_DASHBOARD_DRILLDOWN; @@ -142,14 +142,13 @@ export class DashboardToDashboardDrilldown } } - return this.params - .start() - .plugins.share.urlGenerators.getUrlGenerator(DASHBOARD_APP_URL_GENERATOR) - .createUrl({ - dashboardId: config.dashboardId, - query: config.useCurrentFilters ? query : undefined, - timeRange, - filters: [...existingFilters, ...filtersFromEvent], - }); + const { plugins } = this.params.start(); + + return plugins.share.urlGenerators.getUrlGenerator(DASHBOARD_APP_URL_GENERATOR).createUrl({ + dashboardId: config.dashboardId, + query: config.useCurrentFilters ? query : undefined, + timeRange, + filters: [...existingFilters, ...filtersFromEvent], + }); }; } 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 9daa485bb6e6c5..914f34980a2722 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,6 @@ export { Params as DashboardToDashboardDrilldownParams, } from './drilldown'; export { - PlaceContext as DashboardToDashboardPlaceContext, 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 index 530d48e216530a..1fbff0a7269e26 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 @@ -5,13 +5,11 @@ */ import { - EmbeddableContext, ValueClickTriggerContext, RangeSelectTriggerContext, IEmbeddable, } from '../../../../../../../src/plugins/embeddable/public'; -export type PlaceContext = EmbeddableContext; export type ActionContext = | ValueClickTriggerContext | RangeSelectTriggerContext; 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 75d66b2af8db5c..e8a477de7685e9 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,7 +12,6 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, UiActionsEnhancedSerializedAction, UiActionsEnhancedSerializedEvent, - UiActionsEnhancedDrilldownActionFactoryContext as DrilldownFactoryContext, } from '../../../../advanced_ui_actions/public'; import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; From ebcd5799680468ffd26b33d050724fd878223cc2 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 12:31:43 +0200 Subject: [PATCH 128/129] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20type=20chec?= =?UTF-8?q?k=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connected_flyout_manage_drilldowns.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 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 e8a477de7685e9..0d4a67e325e4dd 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 @@ -73,7 +73,7 @@ export function createFlyoutManageDrilldowns({ [] ); - const factoryContext: DrilldownFactoryContext = React.useMemo( + const factoryContext: object = React.useMemo( () => ({ placeContext: props.placeContext, triggers: selectedTriggers, @@ -122,7 +122,7 @@ export function createFlyoutManageDrilldowns({ return { actionFactory: allActionFactoriesById[drilldownToEdit.action.factoryId], - actionConfig: drilldownToEdit.action.config as object, // TODO: config is unknown, but we know it always extends object + actionConfig: drilldownToEdit.action.config as object, name: drilldownToEdit.action.name, }; } From d50c59c805cb31b3e16c866c771e0d08527966db Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 4 May 2020 12:51:36 +0200 Subject: [PATCH 129/129] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20show=20warning?= =?UTF-8?q?=20message=20if=20embeddable=20not=20provided?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-pack/plugins/embeddable_enhanced/public/plugin.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index ad99b034963bc0..d48c4f9e860cc2 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -113,8 +113,15 @@ export class EmbeddableEnhancedPlugin const storage = new EmbeddableActionStorage(embeddable as EmbeddableWithDynamicActions); const dynamicActions = new DynamicActionManager({ - isCompatible: async (context: unknown) => - (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId, + isCompatible: async (context: unknown) => { + if (!(context as EmbeddableContext)?.embeddable) { + // eslint-disable-next-line no-console + console.warn('For drilldowns to work action context should contain .embeddable field.'); + return false; + } + + return (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId; + }, storage, uiActions: this.uiActions!, });