From 3b31d81196799a9ced9acd5f30082b0f7aed1ce7 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Sat, 17 Apr 2021 22:29:27 -0700 Subject: [PATCH] [Dashboard] Makes lens default editor for creating new panels (#96181) * Makes lens default editor in dashboard Added all editors menu to dashboard panel toolbar Fixed toggle on editor menu Removed unnecessary comments Added data test subjects to editor menu buttons Populated editor menu with vis types Removed unused imports Fixed imports Adds showCreateNewMenu prop to AddPanelFlyout Rearranged order of editor menu options Fixed ts errors Added groupnig to embeddable factory Use embeddable state transfer service to redirect to editors Added showGroups to TypeSelectionState Fixed add panel flyout test Fixed data test subjects Fixed factory groupings Removed unused import Fixed page object Added telemtry to dashboard toolbar Added telemtry to editor menu Fix ml embeddable functional tests Fix lens dashboard test Fix empty dashboard test Fixed ts errors Fixed time to visualize security test Fixed empty dashboard test Fixed clickAddNewEmbeddableLink in dashboardAddPanel service Fixed agg based vis functional tests Revert test changes Fixed typo Fix tests Fix more tests Fix ts errors Fixed more tests Fixed toolbar sizes and margins to align with lens Fix tests Fixed callbacks Fixed button prop type New vis modal copy updates Added savedObjectMetaData to log stream embeddable factory Addressed feedback Fixed ts error Fix more tests Fixed ts errors Updated dashboard empty prompt copy Adds tooltip to log stream embeddable factory saved object meta data Made icons monochrome in toolbar Fixed icon colors in dark mode Cleaned up css Fixed ts errors Updated snapshot Fixed map icon color * Added tooltips for ML embeddables * Restored test * Added empty dashboard panel test * Fixed i18n id * Fix dashboard_embedding test * Removed unused service * Fixed i18n error * Added icon and description properties to embeddable factory definition * Fixed ts errors * Fixed expected value Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...public.embeddablefactory.getdescription.md | 17 ++ ...le-public.embeddablefactory.geticontype.md | 17 ++ ...dable-public.embeddablefactory.grouping.md | 13 + ...ins-embeddable-public.embeddablefactory.md | 3 + ...able-public.embeddablefactorydefinition.md | 2 +- ...ns-embeddable-public.openaddpanelflyout.md | 3 +- src/plugins/dashboard/kibana.json | 3 +- .../public/application/_dashboard_app.scss | 15 +- .../public/application/dashboard_router.tsx | 2 + .../dashboard_container_factory.tsx | 2 +- .../dashboard_empty_screen.test.tsx.snap | 2 +- .../listing/dashboard_listing.test.tsx | 2 + .../application/top_nav/dashboard_top_nav.tsx | 131 ++++++--- .../application/top_nav/editor_menu.tsx | 255 ++++++++++++++++++ .../dashboard/public/application/types.ts | 2 + .../dashboard/public/dashboard_strings.ts | 2 +- src/plugins/dashboard/public/plugin.tsx | 2 + .../default_embeddable_factory_provider.ts | 3 + .../lib/embeddables/embeddable_factory.ts | 17 ++ .../embeddable_factory_definition.ts | 3 + .../add_panel/add_panel_flyout.test.tsx | 2 + .../add_panel/add_panel_flyout.tsx | 5 +- .../add_panel/open_add_panel_flyout.tsx | 3 + src/plugins/embeddable/public/public.api.md | 7 +- .../solution_toolbar/items/button.scss | 1 - .../solution_toolbar/items/button.tsx | 10 +- .../solution_toolbar/items/popover.tsx | 10 +- .../items/primary_button.scss | 20 ++ .../solution_toolbar/items/primary_button.tsx | 18 +- .../solution_toolbar/items/quick_group.scss | 13 + .../solution_toolbar/items/quick_group.tsx | 12 +- .../solution_toolbar/solution_toolbar.scss | 15 +- .../solution_toolbar/solution_toolbar.tsx | 9 +- src/plugins/presentation_util/public/index.ts | 1 + .../visualize_embeddable_factory.tsx | 2 +- src/plugins/visualizations/public/index.ts | 2 +- .../public/wizard/dialog_navigation.tsx | 2 +- .../public/wizard/new_vis_modal.tsx | 7 +- .../public/wizard/show_new_vis.tsx | 7 + test/examples/embeddables/adding_children.ts | 23 +- .../dashboard/create_and_add_embeddables.ts | 26 +- .../dashboard/dashboard_unsaved_listing.ts | 8 +- .../apps/dashboard/dashboard_unsaved_state.ts | 4 +- .../dashboard/edit_embeddable_redirects.ts | 8 +- .../apps/dashboard/edit_visualizations.js | 3 +- .../apps/dashboard/empty_dashboard.ts | 9 +- test/functional/apps/dashboard/view_edit.ts | 6 +- .../functional/page_objects/dashboard_page.ts | 10 - .../services/dashboard/add_panel.ts | 30 ++- .../services/dashboard/visualizations.ts | 45 +--- .../new_visualize_flow/dashboard_embedding.ts | 5 - .../log_stream_embeddable_factory.ts | 10 + .../anomaly_charts_embeddable_factory.ts | 17 +- .../anomaly_swimlane_embeddable_factory.ts | 17 +- .../apps/ml_embeddables_in_dashboard.ts | 4 +- .../apps/dashboard/dashboard_lens_by_value.ts | 2 - .../apps/dashboard/dashboard_maps_by_value.ts | 9 +- .../time_to_visualize_security.ts | 7 +- .../functional/apps/dashboard/sync_colors.ts | 2 - .../dashboard_mode/dashboard_empty_screen.js | 7 - x-pack/test/functional/apps/lens/dashboard.ts | 2 - .../test/functional/apps/lens/lens_tagging.ts | 5 +- .../maps/embeddable/embeddable_library.js | 4 +- .../apps/maps/embeddable/save_and_return.js | 6 +- .../anomaly_charts_dashboard_embeddables.ts | 4 +- .../test/functional/page_objects/lens_page.ts | 3 +- 66 files changed, 686 insertions(+), 232 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.grouping.md create mode 100644 src/plugins/dashboard/public/application/top_nav/editor_menu.tsx create mode 100644 src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.scss diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md new file mode 100644 index 000000000000..1699351349bf --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableFactory](./kibana-plugin-plugins-embeddable-public.embeddablefactory.md) > [getDescription](./kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md) + +## EmbeddableFactory.getDescription() method + +Returns a description about the embeddable. + +Signature: + +```typescript +getDescription(): string; +``` +Returns: + +`string` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md new file mode 100644 index 000000000000..58b987e5630c --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableFactory](./kibana-plugin-plugins-embeddable-public.embeddablefactory.md) > [getIconType](./kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md) + +## EmbeddableFactory.getIconType() method + +Returns an EUI Icon type to be displayed in a menu. + +Signature: + +```typescript +getIconType(): string; +``` +Returns: + +`string` + diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.grouping.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.grouping.md new file mode 100644 index 000000000000..c4dbe739ddfc --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.grouping.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableFactory](./kibana-plugin-plugins-embeddable-public.embeddablefactory.md) > [grouping](./kibana-plugin-plugins-embeddable-public.embeddablefactory.grouping.md) + +## EmbeddableFactory.grouping property + +Indicates the grouping this factory should appear in a sub-menu. Example, this is used for grouping options in the editors menu in Dashboard for creating new embeddables + +Signature: + +```typescript +readonly grouping?: UiActionsPresentableGrouping; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.md index b355acd0567a..8ee60e1f58a2 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactory.md @@ -16,6 +16,7 @@ export interface EmbeddableFactoryUiActionsPresentableGrouping | Indicates the grouping this factory should appear in a sub-menu. Example, this is used for grouping options in the editors menu in Dashboard for creating new embeddables | | [isContainerType](./kibana-plugin-plugins-embeddable-public.embeddablefactory.iscontainertype.md) | boolean | True if is this factory create embeddables that are Containers. Used in the add panel to conditionally show whether these can be added to another container. It's just not supported right now, but once nested containers are officially supported we can probably get rid of this interface. | | [isEditable](./kibana-plugin-plugins-embeddable-public.embeddablefactory.iseditable.md) | () => Promise<boolean> | Returns whether the current user should be allowed to edit this type of embeddable. Most of the time this should be based off the capabilities service, hence it's async. | | [savedObjectMetaData](./kibana-plugin-plugins-embeddable-public.embeddablefactory.savedobjectmetadata.md) | SavedObjectMetaData<TSavedObjectAttributes> | | @@ -29,6 +30,8 @@ export interface EmbeddableFactoryThis will likely change in future iterations when we improve in place editing capabilities. | | [createFromSavedObject(savedObjectId, input, parent)](./kibana-plugin-plugins-embeddable-public.embeddablefactory.createfromsavedobject.md) | Creates a new embeddable instance based off the saved object id. | | [getDefaultInput(partial)](./kibana-plugin-plugins-embeddable-public.embeddablefactory.getdefaultinput.md) | Can be used to get any default input, to be passed in to during the creation process. Default input will not be stored in a parent container, so any inherited input from a container will trump default input parameters. | +| [getDescription()](./kibana-plugin-plugins-embeddable-public.embeddablefactory.getdescription.md) | Returns a description about the embeddable. | | [getDisplayName()](./kibana-plugin-plugins-embeddable-public.embeddablefactory.getdisplayname.md) | Returns a display name for this type of embeddable. Used in "Create new... " options in the add panel for containers. | | [getExplicitInput()](./kibana-plugin-plugins-embeddable-public.embeddablefactory.getexplicitinput.md) | Can be used to request explicit input from the user, to be passed in to EmbeddableFactory:create. Explicit input is stored on the parent container for this embeddable. It overrides any inherited input passed down from the parent container. | +| [getIconType()](./kibana-plugin-plugins-embeddable-public.embeddablefactory.geticontype.md) | Returns an EUI Icon type to be displayed in a menu. | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactorydefinition.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactorydefinition.md index 6ecb88e7c017..dd6127262516 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactorydefinition.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablefactorydefinition.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type EmbeddableFactoryDefinition = IEmbeddable, T extends SavedObjectAttributes = SavedObjectAttributes> = Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> & Partial, 'createFromSavedObject' | 'isContainerType' | 'getExplicitInput' | 'savedObjectMetaData' | 'canCreateNew' | 'getDefaultInput' | 'telemetry' | 'extract' | 'inject' | 'migrations'>>; +export declare type EmbeddableFactoryDefinition = IEmbeddable, T extends SavedObjectAttributes = SavedObjectAttributes> = Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> & Partial, 'createFromSavedObject' | 'isContainerType' | 'getExplicitInput' | 'savedObjectMetaData' | 'canCreateNew' | 'getDefaultInput' | 'telemetry' | 'extract' | 'inject' | 'migrations' | 'grouping' | 'getIconType' | 'getDescription'>>; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md index add464637535..90caaa3035b3 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md @@ -14,6 +14,7 @@ export declare function openAddPanelFlyout(options: { overlays: OverlayStart; notifications: NotificationsStart; SavedObjectFinder: React.ComponentType; + showCreateNewMenu?: boolean; }): OverlayRef; ``` @@ -21,7 +22,7 @@ export declare function openAddPanelFlyout(options: { | Parameter | Type | Description | | --- | --- | --- | -| options | {
embeddable: IContainer;
getFactory: EmbeddableStart['getEmbeddableFactory'];
getAllFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: OverlayStart;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType<any>;
} | | +| options | {
embeddable: IContainer;
getFactory: EmbeddableStart['getEmbeddableFactory'];
getAllFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: OverlayStart;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType<any>;
showCreateNewMenu?: boolean;
} | | Returns: diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 41335069461f..54eaf461b73d 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -11,7 +11,8 @@ "share", "uiActions", "urlForwarding", - "presentationUtil" + "presentationUtil", + "visualizations" ], "optionalPlugins": [ "home", diff --git a/src/plugins/dashboard/public/application/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss index 30253afff391..f6525377cce7 100644 --- a/src/plugins/dashboard/public/application/_dashboard_app.scss +++ b/src/plugins/dashboard/public/application/_dashboard_app.scss @@ -66,4 +66,17 @@ .dshUnsavedListingItem__actions { flex-direction: column; } -} \ No newline at end of file +} + +// Temporary fix for two tone icons to make them monochrome +.dshSolutionToolbar__editorContextMenu--dark { + .euiIcon path { + fill: $euiColorGhost; + } +} + +.dshSolutionToolbar__editorContextMenu--light { + .euiIcon path { + fill: $euiColorInk; + } +} diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index e5281a257ee1..ed68afc5e97b 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -80,6 +80,7 @@ export async function mountApp({ embeddable: embeddableStart, kibanaLegacy: { dashboardConfig }, savedObjectsTaggingOss, + visualizations, } = pluginsStart; const spacesApi = pluginsStart.spacesOss?.isSpacesAvailable ? pluginsStart.spacesOss : undefined; @@ -123,6 +124,7 @@ export async function mountApp({ visualizeCapabilities: { save: Boolean(coreStart.application.capabilities.visualize?.save) }, storeSearchSession: Boolean(coreStart.application.capabilities.dashboard.storeSearchSession), }, + visualizations, }; const getUrlStateStorage = (history: RouteComponentProps['history']) => diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 9b93f0bbd071..ff592742488f 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -49,7 +49,7 @@ export class DashboardContainerFactoryDefinition public readonly getDisplayName = () => { return i18n.translate('dashboard.factory.displayName', { - defaultMessage: 'dashboard', + defaultMessage: 'Dashboard', }); }; diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index 4cd3eb13f360..138d665866af 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -287,7 +287,7 @@ exports[`DashboardEmptyScreen renders correctly with edit mode 1`] = `

- Add your first panel + Add your first visualization

().services; const [state, setState] = useState({ chromeIsVisible: false }); const [isSaveInProgress, setIsSaveInProgress] = useState(false); + const lensAlias = visualizations.getAliases().find(({ name }) => name === 'lens'); + const quickButtonVisTypes = ['markdown', 'maps']; const stateTransferService = embeddable.getStateTransfer(); + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + + const trackUiMetric = usageCollection?.reportUiCounter.bind( + usageCollection, + DashboardConstants.DASHBOARDS_ID + ); useEffect(() => { const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => { @@ -152,27 +161,36 @@ export function DashboardTopNav({ uiSettings, ]); - const createNew = useCallback(async () => { - const type = 'visualization'; - const factory = embeddable.getEmbeddableFactory(type); - - if (!factory) { - throw new EmbeddableFactoryNotFoundError(type); - } - - await factory.create({} as EmbeddableInput, dashboardContainer); - }, [dashboardContainer, embeddable]); - const createNewVisType = useCallback( - (newVisType: string) => async () => { - stateTransferService.navigateToEditor('visualize', { - path: `#/create?type=${encodeURIComponent(newVisType)}`, + (visType?: BaseVisType | VisTypeAlias) => () => { + let path = ''; + let appId = ''; + + if (visType) { + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, visType.name); + } + + if ('aliasPath' in visType) { + appId = visType.aliasApp; + path = visType.aliasPath; + } else { + appId = 'visualize'; + path = `#/create?type=${encodeURIComponent(visType.name)}`; + } + } else { + appId = 'visualize'; + path = '#/create?'; + } + + stateTransferService.navigateToEditor(appId, { + path, state: { originatingApp: DashboardConstants.DASHBOARDS_ID, }, }); }, - [stateTransferService] + [trackUiMetric, stateTransferService] ); const clearAddPanel = useCallback(() => { @@ -563,38 +581,57 @@ export function DashboardTopNav({ const { TopNavMenu } = navigation.ui; - const quickButtons = [ - { - iconType: 'visText', - createType: i18n.translate('dashboard.solutionToolbar.markdownQuickButtonLabel', { - defaultMessage: 'Markdown', - }), - onClick: createNewVisType('markdown'), - 'data-test-subj': 'dashboardMarkdownQuickButton', - }, - { - iconType: 'controlsHorizontal', - createType: i18n.translate('dashboard.solutionToolbar.inputControlsQuickButtonLabel', { - defaultMessage: 'Input control', - }), - onClick: createNewVisType('input_control_vis'), - 'data-test-subj': 'dashboardInputControlsQuickButton', - }, - ]; + const getVisTypeQuickButton = (visTypeName: string) => { + const visType = + visualizations.get(visTypeName) || + visualizations.getAliases().find(({ name }) => name === visTypeName); + + if (visType) { + if ('aliasPath' in visType) { + const { name, icon, title } = visType as VisTypeAlias; + + return { + iconType: icon, + createType: title, + onClick: createNewVisType(visType as VisTypeAlias), + 'data-test-subj': `dashboardQuickButton${name}`, + isDarkModeEnabled: IS_DARK_THEME, + }; + } else { + const { name, icon, title, titleInWizard } = visType as BaseVisType; + + return { + iconType: icon, + createType: titleInWizard || title, + onClick: createNewVisType(visType as BaseVisType), + 'data-test-subj': `dashboardQuickButton${name}`, + isDarkModeEnabled: IS_DARK_THEME, + }; + } + } + + return; + }; + + const quickButtons = quickButtonVisTypes + .map(getVisTypeQuickButton) + .filter((button) => button) as QuickButtonProps[]; return ( <> + {viewMode !== ViewMode.VIEW ? ( - + {{ primaryActionButton: ( ), @@ -605,6 +642,12 @@ export function DashboardTopNav({ data-test-subj="dashboardAddPanelButton" /> ), + extraButtons: [ + , + ], }} ) : null} diff --git a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx new file mode 100644 index 000000000000..5205f5b294c4 --- /dev/null +++ b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx @@ -0,0 +1,255 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, + EuiContextMenuItemIcon, +} from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { i18n } from '@kbn/i18n'; +import { BaseVisType, VisGroups, VisTypeAlias } from '../../../../visualizations/public'; +import { SolutionToolbarPopover } from '../../../../presentation_util/public'; +import { EmbeddableFactoryDefinition, EmbeddableInput } from '../../services/embeddable'; +import { useKibana } from '../../services/kibana_react'; +import { DashboardAppServices } from '../types'; +import { DashboardContainer } from '..'; +import { DashboardConstants } from '../../dashboard_constants'; +import { dashboardReplacePanelAction } from '../../dashboard_strings'; + +interface Props { + /** Dashboard container */ + dashboardContainer: DashboardContainer; + /** Handler for creating new visualization of a specified type */ + createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void; +} + +interface FactoryGroup { + id: string; + appName: string; + icon: EuiContextMenuItemIcon; + panelId: number; + factories: EmbeddableFactoryDefinition[]; +} + +export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { + const { + core, + embeddable, + visualizations, + usageCollection, + uiSettings, + } = useKibana().services; + + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + + const trackUiMetric = usageCollection?.reportUiCounter.bind( + usageCollection, + DashboardConstants.DASHBOARDS_ID + ); + + const createNewAggsBasedVis = useCallback( + (visType?: BaseVisType) => () => + visualizations.showNewVisModal({ + originatingApp: DashboardConstants.DASHBOARDS_ID, + outsideVisualizeApp: true, + showAggsSelection: true, + selectedVisType: visType, + }), + [visualizations] + ); + + const getVisTypesByGroup = (group: VisGroups) => + visualizations + .getByGroup(group) + .sort(({ name: a }: BaseVisType | VisTypeAlias, { name: b }: BaseVisType | VisTypeAlias) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }) + .filter(({ hidden }: BaseVisType) => !hidden); + + const promotedVisTypes = getVisTypesByGroup(VisGroups.PROMOTED); + const aggsBasedVisTypes = getVisTypesByGroup(VisGroups.AGGBASED); + const toolVisTypes = getVisTypesByGroup(VisGroups.TOOLS); + const visTypeAliases = visualizations + .getAliases() + .sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) => + a === b ? 0 : a ? -1 : 1 + ); + + const factories = embeddable + ? Array.from(embeddable.getEmbeddableFactories()).filter( + ({ type, isEditable, canCreateNew, isContainerType }) => + isEditable() && !isContainerType && canCreateNew() && type !== 'visualization' + ) + : []; + + const factoryGroupMap: Record = {}; + const ungroupedFactories: EmbeddableFactoryDefinition[] = []; + const aggBasedPanelID = 1; + + let panelCount = 1 + aggBasedPanelID; + + factories.forEach((factory: EmbeddableFactoryDefinition, index) => { + const { grouping } = factory; + + if (grouping) { + grouping.forEach((group) => { + if (factoryGroupMap[group.id]) { + factoryGroupMap[group.id].factories.push(factory); + } else { + factoryGroupMap[group.id] = { + id: group.id, + appName: group.getDisplayName ? group.getDisplayName({ embeddable }) : group.id, + icon: (group.getIconType + ? group.getIconType({ embeddable }) + : 'empty') as EuiContextMenuItemIcon, + factories: [factory], + panelId: panelCount, + }; + + panelCount++; + } + }); + } else { + ungroupedFactories.push(factory); + } + }); + + const getVisTypeMenuItem = (visType: BaseVisType): EuiContextMenuPanelItemDescriptor => { + const { name, title, titleInWizard, description, icon = 'empty', group } = visType; + return { + name: titleInWizard || title, + icon: icon as string, + onClick: + group === VisGroups.AGGBASED ? createNewAggsBasedVis(visType) : createNewVisType(visType), + 'data-test-subj': `visType-${name}`, + toolTipContent: description, + }; + }; + + const getVisTypeAliasMenuItem = ( + visTypeAlias: VisTypeAlias + ): EuiContextMenuPanelItemDescriptor => { + const { name, title, description, icon = 'empty' } = visTypeAlias; + + return { + name: title, + icon, + onClick: createNewVisType(visTypeAlias), + 'data-test-subj': `visType-${name}`, + toolTipContent: description, + }; + }; + + const getEmbeddableFactoryMenuItem = ( + factory: EmbeddableFactoryDefinition + ): EuiContextMenuPanelItemDescriptor => { + const icon = factory?.getIconType ? factory.getIconType() : 'empty'; + + const toolTipContent = factory?.getDescription ? factory.getDescription() : undefined; + + return { + name: factory.getDisplayName(), + icon, + toolTipContent, + onClick: async () => { + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, factory.type); + } + let newEmbeddable; + if (factory.getExplicitInput) { + const explicitInput = await factory.getExplicitInput(); + newEmbeddable = await dashboardContainer.addNewEmbeddable(factory.type, explicitInput); + } else { + newEmbeddable = await factory.create({} as EmbeddableInput, dashboardContainer); + } + + if (newEmbeddable) { + core.notifications.toasts.addSuccess({ + title: dashboardReplacePanelAction.getSuccessMessage( + `'${newEmbeddable.getInput().title}'` || '' + ), + 'data-test-subj': 'addEmbeddableToDashboardSuccess', + }); + } + }, + 'data-test-subj': `createNew-${factory.type}`, + }; + }; + + const aggsPanelTitle = i18n.translate('dashboard.editorMenu.aggBasedGroupTitle', { + defaultMessage: 'Aggregation based', + }); + + const editorMenuPanels = [ + { + id: 0, + items: [ + ...visTypeAliases.map(getVisTypeAliasMenuItem), + ...Object.values(factoryGroupMap).map(({ id, appName, icon, panelId }) => ({ + name: appName, + icon, + panel: panelId, + 'data-test-subj': `dashboardEditorMenu-${id}Group`, + })), + ...ungroupedFactories.map(getEmbeddableFactoryMenuItem), + ...promotedVisTypes.map(getVisTypeMenuItem), + { + name: aggsPanelTitle, + icon: 'visualizeApp', + panel: aggBasedPanelID, + 'data-test-subj': `dashboardEditorAggBasedMenuItem`, + }, + ...toolVisTypes.map(getVisTypeMenuItem), + ], + }, + { + id: aggBasedPanelID, + title: aggsPanelTitle, + items: aggsBasedVisTypes.map(getVisTypeMenuItem), + }, + ...Object.values(factoryGroupMap).map( + ({ appName, panelId, factories: groupFactories }: FactoryGroup) => ({ + id: panelId, + title: appName, + items: groupFactories.map(getEmbeddableFactoryMenuItem), + }) + ), + ]; + + return ( + + + + ); +}; diff --git a/src/plugins/dashboard/public/application/types.ts b/src/plugins/dashboard/public/application/types.ts index 6415fdfd73ee..dd291291ce9d 100644 --- a/src/plugins/dashboard/public/application/types.ts +++ b/src/plugins/dashboard/public/application/types.ts @@ -25,6 +25,7 @@ import { DataPublicPluginStart, IndexPatternsContract } from '../services/data'; import { SavedObjectLoader, SavedObjectsStart } from '../services/saved_objects'; import { DashboardPanelStorage } from './lib'; import { UrlForwardingStart } from '../../../url_forwarding/public'; +import { VisualizationsStart } from '../../../visualizations/public'; export type DashboardRedirect = (props: RedirectToProps) => void; export type RedirectToProps = @@ -83,4 +84,5 @@ export interface DashboardAppServices { savedObjectsClient: SavedObjectsClientContract; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; savedQueryService: DataPublicPluginStart['query']['savedQueries']; + visualizations: VisualizationsStart; } diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 79a59d0cfa60..531ff815312c 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -377,7 +377,7 @@ export const emptyScreenStrings = { }), getEmptyWidgetTitle: () => i18n.translate('dashboard.emptyWidget.addPanelTitle', { - defaultMessage: 'Add your first panel', + defaultMessage: 'Add your first visualization', }), getEmptyWidgetDescription: () => i18n.translate('dashboard.emptyWidget.addPanelDescription', { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index e2f52a47455b..0fad1c51f433 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -24,6 +24,7 @@ import { PluginInitializerContext, SavedObjectsClientContract, } from '../../../core/public'; +import { VisualizationsStart } from '../../visualizations/public'; import { createKbnUrlTracker } from './services/kibana_utils'; import { UsageCollectionSetup } from './services/usage_collection'; @@ -115,6 +116,7 @@ export interface DashboardStartDependencies { presentationUtil: PresentationUtilPluginStart; savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; spacesOss?: SpacesOssPluginStart; + visualizations: VisualizationsStart; } export type DashboardSetup = void; diff --git a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts index 27164b3cddbc..b260c594591f 100644 --- a/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts +++ b/src/plugins/embeddable/public/lib/embeddables/default_embeddable_factory_provider.ts @@ -37,11 +37,14 @@ export const defaultEmbeddableFactoryProvider = < type: def.type, isEditable: def.isEditable.bind(def), getDisplayName: def.getDisplayName.bind(def), + getDescription: def.getDescription ? def.getDescription.bind(def) : () => '', + getIconType: def.getIconType ? def.getIconType.bind(def) : () => 'empty', savedObjectMetaData: def.savedObjectMetaData, telemetry: def.telemetry || (() => ({})), inject: def.inject || ((state: EmbeddableStateWithType) => state), extract: def.extract || ((state: EmbeddableStateWithType) => ({ state, references: [] })), migrations: def.migrations || {}, + grouping: def.grouping, }; return factory; }; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts index 7f3277130f90..6ec035f442dd 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory.ts @@ -14,6 +14,7 @@ import { IContainer } from '../containers/i_container'; import { PropertySpec } from '../types'; import { PersistableState } from '../../../../kibana_utils/common'; import { EmbeddableStateWithType } from '../../../common/types'; +import { UiActionsPresentableGrouping } from '../../../../ui_actions/public'; export interface EmbeddableInstanceConfiguration { id: string; @@ -48,6 +49,12 @@ export interface EmbeddableFactory< readonly savedObjectMetaData?: SavedObjectMetaData; + /** + * Indicates the grouping this factory should appear in a sub-menu. Example, this is used for grouping + * options in the editors menu in Dashboard for creating new embeddables + */ + readonly grouping?: UiActionsPresentableGrouping; + /** * True if is this factory create embeddables that are Containers. Used in the add panel to * conditionally show whether these can be added to another container. It's just not @@ -62,6 +69,16 @@ export interface EmbeddableFactory< */ getDisplayName(): string; + /** + * Returns an EUI Icon type to be displayed in a menu. + */ + getIconType(): string; + + /** + * Returns a description about the embeddable. + */ + getDescription(): string; + /** * If false, this type of embeddable can't be created with the "createNew" functionality. Instead, * use createFromSavedObject, where an existing saved object must first exist. diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts index a64aa32c6e7c..f2819f2a2e66 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_factory_definition.ts @@ -33,5 +33,8 @@ export type EmbeddableFactoryDefinition< | 'extract' | 'inject' | 'migrations' + | 'grouping' + | 'getIconType' + | 'getDescription' > >; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx index 432897763aa0..1c96945f014c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx @@ -61,6 +61,7 @@ test('createNewEmbeddable() add embeddable to container', async () => { getAllFactories={start.getEmbeddableFactories} notifications={core.notifications} SavedObjectFinder={() => null} + showCreateNewMenu /> ) as ReactWrapper; @@ -112,6 +113,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()' getAllFactories={start.getEmbeddableFactories} notifications={core.notifications} SavedObjectFinder={(props) => } + showCreateNewMenu /> ) as ReactWrapper; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 8caec4a4428c..6d6a68d7e5e2 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -26,6 +26,7 @@ interface Props { getAllFactories: EmbeddableStart['getEmbeddableFactories']; notifications: CoreSetup['notifications']; SavedObjectFinder: React.ComponentType; + showCreateNewMenu?: boolean; } interface State { @@ -134,7 +135,9 @@ export class AddPanelFlyout extends React.Component { defaultMessage: 'No matching objects found.', })} > - + {this.props.showCreateNewMenu ? ( + + ) : null} ); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index bed97c82095c..f0c6e81644b3 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -20,6 +20,7 @@ export function openAddPanelFlyout(options: { overlays: OverlayStart; notifications: NotificationsStart; SavedObjectFinder: React.ComponentType; + showCreateNewMenu?: boolean; }): OverlayRef { const { embeddable, @@ -28,6 +29,7 @@ export function openAddPanelFlyout(options: { overlays, notifications, SavedObjectFinder, + showCreateNewMenu, } = options; const flyoutSession = overlays.openFlyout( toMountPoint( @@ -42,6 +44,7 @@ export function openAddPanelFlyout(options: { getAllFactories={getAllFactories} notifications={notifications} SavedObjectFinder={SavedObjectFinder} + showCreateNewMenu={showCreateNewMenu} /> ), { diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 220039de2f34..d522a4e5fa8e 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -378,8 +378,12 @@ export interface EmbeddableFactory; createFromSavedObject(savedObjectId: string, input: Partial, parent?: IContainer): Promise; getDefaultInput(partial: Partial): Partial; + getDescription(): string; getDisplayName(): string; getExplicitInput(): Promise>; + getIconType(): string; + // Warning: (ae-forgotten-export) The symbol "PresentableGrouping" needs to be exported by the entry point index.d.ts + readonly grouping?: PresentableGrouping; readonly isContainerType: boolean; readonly isEditable: () => Promise; // Warning: (ae-forgotten-export) The symbol "SavedObjectMetaData" needs to be exported by the entry point index.d.ts @@ -393,7 +397,7 @@ export interface EmbeddableFactory = IEmbeddable, T extends SavedObjectAttributes = SavedObjectAttributes> = Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> & Partial, 'createFromSavedObject' | 'isContainerType' | 'getExplicitInput' | 'savedObjectMetaData' | 'canCreateNew' | 'getDefaultInput' | 'telemetry' | 'extract' | 'inject' | 'migrations'>>; +export type EmbeddableFactoryDefinition = IEmbeddable, T extends SavedObjectAttributes = SavedObjectAttributes> = Pick, 'create' | 'type' | 'isEditable' | 'getDisplayName'> & Partial, 'createFromSavedObject' | 'isContainerType' | 'getExplicitInput' | 'savedObjectMetaData' | 'canCreateNew' | 'getDefaultInput' | 'telemetry' | 'extract' | 'inject' | 'migrations' | 'grouping' | 'getIconType' | 'getDescription'>>; // Warning: (ae-missing-release-tag) "EmbeddableFactoryNotFoundError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -724,6 +728,7 @@ export function openAddPanelFlyout(options: { overlays: OverlayStart_2; notifications: NotificationsStart_2; SavedObjectFinder: React.ComponentType; + showCreateNewMenu?: boolean; }): OverlayRef_2; // Warning: (ae-missing-release-tag) "OutputSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss index 79c3d4cca7ac..b8022201acf5 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss @@ -1,4 +1,3 @@ - .solutionToolbarButton { line-height: $euiButtonHeight; // Keeps alignment of text and chart icon background-color: $euiColorEmptyShade; diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.tsx index 5de8e24ef5f0..ee1bbd64b5f8 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.tsx @@ -12,17 +12,19 @@ import { EuiButtonPropsForButton } from '@elastic/eui/src/components/button/butt import './button.scss'; -export interface Props extends Pick { +export interface Props + extends Pick { label: string; primary?: boolean; + isDarkModeEnabled?: boolean; } -export const SolutionToolbarButton = ({ label, primary, ...rest }: Props) => ( +export const SolutionToolbarButton = ({ label, primary, className, ...rest }: Props) => ( {label} diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx index fbb34e165190..33850005b498 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx @@ -20,14 +20,20 @@ type AllowedPopoverProps = Omit< export type Props = AllowedButtonProps & AllowedPopoverProps; -export const SolutionToolbarPopover = ({ label, iconType, primary, ...popover }: Props) => { +export const SolutionToolbarPopover = ({ + label, + iconType, + primary, + iconSide, + ...popover +}: Props) => { const [isOpen, setIsOpen] = useState(false); const onButtonClick = () => setIsOpen((status) => !status); const closePopover = () => setIsOpen(false); const button = ( - + ); return ( diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.scss new file mode 100644 index 000000000000..c3d89f430d70 --- /dev/null +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.scss @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Temporary fix for lensApp icon not support ghost color +.solutionToolbar__primaryButton--dark { + .euiIcon path { + fill: $euiColorInk; + } +} + +.solutionToolbar__primaryButton--light { + .euiIcon path { + fill: $euiColorGhost; + } +} diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.tsx index e2ef75e45a40..dcf16228ac63 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/primary_button.tsx @@ -10,6 +10,20 @@ import React from 'react'; import { SolutionToolbarButton, Props as SolutionToolbarButtonProps } from './button'; -export const PrimaryActionButton = (props: Omit) => ( - +import './primary_button.scss'; + +export interface Props extends Omit { + isDarkModeEnabled?: boolean; +} + +export const PrimaryActionButton = ({ isDarkModeEnabled, ...props }: Props) => ( + ); diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss index 639ff5bf2a11..870a9a945ed5 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss @@ -2,4 +2,17 @@ .quickButtonGroup__button { background-color: $euiColorEmptyShade; } + + // Temporary fix for two tone icons to make them monochrome + .quickButtonGroup__button--dark { + .euiIcon path { + fill: $euiColorGhost; + } + } + // Temporary fix for two tone icons to make them monochrome + .quickButtonGroup__button--light { + .euiIcon path { + fill: $euiColorInk; + } + } } diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.tsx index 58f8bd803b63..eb0a395548cd 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.tsx @@ -17,23 +17,27 @@ import './quick_group.scss'; export interface QuickButtonProps extends Pick { createType: string; onClick: () => void; + isDarkModeEnabled?: boolean; } export interface Props { buttons: QuickButtonProps[]; } -type Option = EuiButtonGroupOptionProps & Omit; +type Option = EuiButtonGroupOptionProps & + Omit; export const QuickButtonGroup = ({ buttons }: Props) => { const buttonGroupOptions: Option[] = buttons.map((button: QuickButtonProps, index) => { - const { createType: label, ...rest } = button; + const { createType: label, isDarkModeEnabled, ...rest } = button; const title = strings.getAriaButtonLabel(label); return { ...rest, 'aria-label': title, - className: 'quickButtonGroup__button', + className: `quickButtonGroup__button ${ + isDarkModeEnabled ? 'quickButtonGroup__button--dark' : 'quickButtonGroup__button--light' + }`, id: `${htmlIdGenerator()()}${index}`, label, title, @@ -46,7 +50,7 @@ export const QuickButtonGroup = ({ buttons }: Props) => { return ( { +export const SolutionToolbar = ({ isDarkModeEnabled, children }: Props) => { const { primaryActionButton, quickButtonGroup, @@ -49,8 +50,10 @@ export const SolutionToolbar = ({ children }: Props) => { return ( {primaryActionButton} diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 9c5f65de4095..fd3ae8941929 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -19,6 +19,7 @@ export { LazySavedObjectSaveModalDashboard, withSuspense, } from './components'; + export { AddFromLibraryButton, PrimaryActionButton, diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 2b5a611cd946..48bff8d203eb 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -113,7 +113,7 @@ export class VisualizeEmbeddableFactory public getDisplayName() { return i18n.translate('visualizations.displayName', { - defaultMessage: 'visualization', + defaultMessage: 'Visualization', }); } diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index e5b1ba73d9d1..dbcbb864d231 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -25,7 +25,7 @@ export { getVisSchemas } from './vis_schemas'; /** @public types */ export { VisualizationsSetup, VisualizationsStart }; export { VisGroups } from './vis_types'; -export type { VisTypeAlias, VisTypeDefinition, Schema, ISchemas } from './vis_types'; +export type { BaseVisType, VisTypeAlias, VisTypeDefinition, Schema, ISchemas } from './vis_types'; export { SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; export type VisualizeEmbeddableContract = PublicContract; diff --git a/src/plugins/visualizations/public/wizard/dialog_navigation.tsx b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx index 1de177e12f40..c92514d54166 100644 --- a/src/plugins/visualizations/public/wizard/dialog_navigation.tsx +++ b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx @@ -24,7 +24,7 @@ function DialogNavigation(props: DialogNavigationProps) { {i18n.translate('visualizations.newVisWizard.goBackLink', { - defaultMessage: 'Go back', + defaultMessage: 'Select a different visualization', })} diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index d36b734f75be..317f9d1bb363 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -41,6 +41,8 @@ interface TypeSelectionProps { outsideVisualizeApp?: boolean; stateTransfer?: EmbeddableStateTransfer; originatingApp?: string; + showAggsSelection?: boolean; + selectedVisType?: BaseVisType; } interface TypeSelectionState { @@ -69,8 +71,9 @@ class NewVisModal extends React.Component import('./new_vis_modal')); @@ -29,6 +30,8 @@ export interface ShowNewVisModalParams { originatingApp?: string; outsideVisualizeApp?: boolean; createByValue?: boolean; + showAggsSelection?: boolean; + selectedVisType?: BaseVisType; } /** @@ -41,6 +44,8 @@ export function showNewVisModal({ onClose, originatingApp, outsideVisualizeApp, + showAggsSelection, + selectedVisType, }: ShowNewVisModalParams = {}) { const container = document.createElement('div'); let isClosed = false; @@ -78,6 +83,8 @@ export function showNewVisModal({ usageCollection={getUsageCollector()} application={getApplication()} docLinks={getDocLinks()} + showAggsSelection={showAggsSelection} + selectedVisType={selectedVisType} /> diff --git a/test/examples/embeddables/adding_children.ts b/test/examples/embeddables/adding_children.ts index 8b59012bf982..ee06622a33f5 100644 --- a/test/examples/embeddables/adding_children.ts +++ b/test/examples/embeddables/adding_children.ts @@ -13,31 +13,12 @@ import { PluginFunctionalProviderContext } from 'test/plugin_functional/services export default function ({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); const flyout = getService('flyout'); - const retry = getService('retry'); - describe('creating and adding children', () => { + describe('adding children', () => { before(async () => { await testSubjects.click('embeddablePanelExample'); }); - it('Can create a new child', async () => { - await testSubjects.click('embeddablePanelToggleMenuIcon'); - await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); - - // this seem like an overkill, but clicking this button which opens context menu was flaky - await testSubjects.waitForEnabled('createNew'); - await retry.waitFor('createNew popover opened', async () => { - await testSubjects.click('createNew'); - return await testSubjects.exists('createNew-TODO_EMBEDDABLE'); - }); - await testSubjects.click('createNew-TODO_EMBEDDABLE'); - - await testSubjects.setValue('taskInputField', 'new task'); - await testSubjects.click('createTodoEmbeddable'); - const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); - expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task']); - }); - it('Can add a child backed off a saved object', async () => { await testSubjects.click('embeddablePanelToggleMenuIcon'); await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL'); @@ -46,7 +27,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { await testSubjects.moveMouseTo('euiFlyoutCloseButton'); await flyout.ensureClosed('dashboardAddPanel'); const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask'); - expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task', 'Take the garbage out']); + expect(tasks).to.eql(['Goes out on Wednesdays!', 'Take the garbage out']); }); }); } diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.ts b/test/functional/apps/dashboard/create_and_add_embeddables.ts index 9b8fc4785a67..3de3b2f843f5 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.ts +++ b/test/functional/apps/dashboard/create_and_add_embeddables.ts @@ -35,8 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adds new visualization via the top nav link', async () => { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await PageObjects.dashboard.switchToEditMode(); - await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( @@ -52,9 +52,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adds a new visualization', async () => { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); - await dashboardAddPanel.ensureAddPanelIsShowing(); - await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( @@ -71,7 +70,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adds a markdown visualization via the quick button', async () => { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); - await PageObjects.dashboard.clickMarkdownQuickButton(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visualize.saveVisualizationExpectSuccess( 'visualization from markdown quick button', { redirectToOrigin: true } @@ -84,21 +83,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); }); - it('adds an input control visualization via the quick button', async () => { - const originalPanelCount = await PageObjects.dashboard.getPanelCount(); - await PageObjects.dashboard.clickInputControlsQuickButton(); - await PageObjects.visualize.saveVisualizationExpectSuccess( - 'visualization from input control quick button', - { redirectToOrigin: true } - ); - - await retry.try(async () => { - const panelCount = await PageObjects.dashboard.getPanelCount(); - expect(panelCount).to.eql(originalPanelCount + 1); - }); - await PageObjects.dashboard.waitForRenderComplete(); - }); - it('saves the listing page instead of the visualization to the app link', async () => { await PageObjects.header.clickVisualize(true); const currentUrl = await browser.getCurrentUrl(); diff --git a/test/functional/apps/dashboard/dashboard_unsaved_listing.ts b/test/functional/apps/dashboard/dashboard_unsaved_listing.ts index 233d2e91467f..1cdc4bbff2c5 100644 --- a/test/functional/apps/dashboard/dashboard_unsaved_listing.ts +++ b/test/functional/apps/dashboard/dashboard_unsaved_listing.ts @@ -25,8 +25,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dashboard unsaved listing', () => { const addSomePanels = async () => { // add an area chart by value - await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationAndReturn(); @@ -132,8 +132,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.switchToEditMode(); // add another panel so we can delete it later - await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess('Wildvis', { diff --git a/test/functional/apps/dashboard/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/dashboard_unsaved_state.ts index e6cc91880010..fd203cd8c135 100644 --- a/test/functional/apps/dashboard/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/dashboard_unsaved_state.ts @@ -41,8 +41,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('shows the unsaved changes badge after adding panels', async () => { await PageObjects.dashboard.switchToEditMode(); // add an area chart by value - await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationAndReturn(); diff --git a/test/functional/apps/dashboard/edit_embeddable_redirects.ts b/test/functional/apps/dashboard/edit_embeddable_redirects.ts index 8b7b98a59aa1..be540e18a503 100644 --- a/test/functional/apps/dashboard/edit_embeddable_redirects.ts +++ b/test/functional/apps/dashboard/edit_embeddable_redirects.ts @@ -13,10 +13,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardVisualizations = getService('dashboardVisualizations'); + const dashboardAddPanel = getService('dashboardAddPanel'); describe('edit embeddable redirects', () => { before(async () => { @@ -88,10 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const newTitle = 'test create panel originatingApp'; await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickMarkdownWidget(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visualize.saveVisualizationExpectSuccess(newTitle, { saveAsNew: true, redirectToOrigin: false, diff --git a/test/functional/apps/dashboard/edit_visualizations.js b/test/functional/apps/dashboard/edit_visualizations.js index ce32f53587e7..b2f21aefcf79 100644 --- a/test/functional/apps/dashboard/edit_visualizations.js +++ b/test/functional/apps/dashboard/edit_visualizations.js @@ -14,13 +14,14 @@ export default function ({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const kibanaServer = getService('kibanaServer'); + const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); const originalMarkdownText = 'Original markdown text'; const modifiedMarkdownText = 'Modified markdown text'; const createMarkdownVis = async (title) => { - await PageObjects.dashboard.clickMarkdownQuickButton(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visEditor.setMarkdownTxt(originalMarkdownText); await PageObjects.visEditor.clickGo(); if (title) { diff --git a/test/functional/apps/dashboard/empty_dashboard.ts b/test/functional/apps/dashboard/empty_dashboard.ts index c096d90aa359..2cfa6d73dcb7 100644 --- a/test/functional/apps/dashboard/empty_dashboard.ts +++ b/test/functional/apps/dashboard/empty_dashboard.ts @@ -41,15 +41,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should open add panel when add button is clicked', async () => { - await testSubjects.click('dashboardAddPanelButton'); + await dashboardAddPanel.clickOpenAddPanel(); const isAddPanelOpen = await dashboardAddPanel.isAddPanelOpen(); expect(isAddPanelOpen).to.be(true); await testSubjects.click('euiFlyoutCloseButton'); }); it('should add new visualization from dashboard', async () => { - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); await dashboardVisualizations.createAndAddMarkdown({ name: 'Dashboard Test Markdown', markdown: 'Markdown text', @@ -57,5 +55,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.markdownWithValuesExists(['Markdown text']); }); + + it('should open editor menu when editor button is clicked', async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await testSubjects.existOrFail('dashboardEditorContextMenu'); + }); }); } diff --git a/test/functional/apps/dashboard/view_edit.ts b/test/functional/apps/dashboard/view_edit.ts index c5c7daab27ff..99a78ebd069c 100644 --- a/test/functional/apps/dashboard/view_edit.ts +++ b/test/functional/apps/dashboard/view_edit.ts @@ -113,10 +113,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('when a new vis is added', async function () { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); - - await dashboardAddPanel.ensureAddPanelIsShowing(); - await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); - await PageObjects.visualize.clickAggBasedVisualizations(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel', { diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 34559afdf6ae..9c12296db138 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -413,16 +413,6 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide await testSubjects.click('confirmSaveSavedObjectButton'); } - public async clickMarkdownQuickButton() { - log.debug('Click markdown quick button'); - await testSubjects.click('dashboardMarkdownQuickButton'); - } - - public async clickInputControlsQuickButton() { - log.debug('Click input controls quick button'); - await testSubjects.click('dashboardInputControlsQuickButton'); - } - /** * * @param dashboardTitle {String} diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index 7bb1603e0193..a4e0c8b2647d 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -30,15 +30,41 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }: FtrPro await PageObjects.common.sleep(500); } + async clickQuickButton(visType: string) { + log.debug(`DashboardAddPanel.clickQuickButton${visType}`); + await testSubjects.click(`dashboardQuickButton${visType}`); + } + + async clickMarkdownQuickButton() { + await this.clickQuickButton('markdown'); + } + + async clickMapQuickButton() { + await this.clickQuickButton('map'); + } + + async clickEditorMenuButton() { + log.debug('DashboardAddPanel.clickEditorMenuButton'); + await testSubjects.click('dashboardEditorMenuButton'); + } + + async clickAggBasedVisualizations() { + log.debug('DashboardAddPanel.clickEditorMenuAggBasedMenuItem'); + await testSubjects.click('dashboardEditorAggBasedMenuItem'); + } + async clickVisType(visType: string) { log.debug('DashboardAddPanel.clickVisType'); await testSubjects.click(`visType-${visType}`); } + async clickEmbeddableFactoryGroupButton(groupId: string) { + log.debug('DashboardAddPanel.clickEmbeddableFactoryGroupButton'); + await testSubjects.click(`dashboardEditorMenu-${groupId}Group`); + } + async clickAddNewEmbeddableLink(type: string) { - await testSubjects.click('createNew'); await testSubjects.click(`createNew-${type}`); - await testSubjects.missingOrFail(`createNew-${type}`); } async toggleFilterPopover() { diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index d1aaa6aa1bd7..2bf7458ff9c5 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -10,8 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function DashboardVisualizationProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); - const find = getService('find'); - const retry = getService('retry'); const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -31,8 +29,8 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await dashboardAddPanel.ensureAddPanelIsShowing(); - await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewEmbeddableLink('metrics'); await PageObjects.visualize.clickVisualBuilder(); await PageObjects.visualize.saveVisualizationExpectSuccess(name); } @@ -87,39 +85,13 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F await dashboardAddPanel.addSavedSearch(name); } - async clickAddVisualizationButton() { - log.debug('DashboardVisualizations.clickAddVisualizationButton'); - await testSubjects.click('dashboardAddNewPanelButton'); - } - - async isNewVisDialogShowing() { - log.debug('DashboardVisualizations.isNewVisDialogShowing'); - return await find.existsByCssSelector('.visNewVisDialog'); - } - - async ensureNewVisualizationDialogIsShowing() { - let isShowing = await this.isNewVisDialogShowing(); - log.debug(`DashboardVisualizations.ensureNewVisualizationDialogIsShowing:${isShowing}`); - if (!isShowing) { - await retry.try(async () => { - await this.clickAddVisualizationButton(); - isShowing = await this.isNewVisDialogShowing(); - log.debug(`DashboardVisualizations.ensureNewVisualizationDialogIsShowing:${isShowing}`); - if (!isShowing) { - throw new Error('New Vis Dialog still not open, trying again.'); - } - }); - } - } - async createAndAddMarkdown({ name, markdown }: { name: string; markdown: string }) { log.debug(`createAndAddMarkdown(${markdown})`); const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await this.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickMarkdownWidget(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visEditor.setMarkdownTxt(markdown); await PageObjects.visEditor.clickGo(); await PageObjects.visualize.saveVisualizationExpectSuccess(name, { @@ -134,10 +106,10 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await this.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickAggBasedVisualizations(); - await PageObjects.visualize.clickMetric(); - await find.clickByCssSelector('li.euiListGroupItem:nth-of-type(2)'); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAggBasedVisualizations(); + await dashboardAddPanel.clickVisType('metric'); + await testSubjects.click('savedObjectTitlelogstash-*'); await testSubjects.exists('visualizesaveAndReturnButton'); await testSubjects.click('visualizesaveAndReturnButton'); } @@ -148,8 +120,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await this.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickMarkdownWidget(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visEditor.setMarkdownTxt(markdown); await PageObjects.visEditor.clickGo(); await testSubjects.click('visualizesaveAndReturnButton'); diff --git a/test/new_visualize_flow/dashboard_embedding.ts b/test/new_visualize_flow/dashboard_embedding.ts index 6a1315dbfc91..04b91542223b 100644 --- a/test/new_visualize_flow/dashboard_embedding.ts +++ b/test/new_visualize_flow/dashboard_embedding.ts @@ -22,7 +22,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const dashboardExpect = getService('dashboardExpect'); - const testSubjects = getService('testSubjects'); const dashboardVisualizations = getService('dashboardVisualizations'); const PageObjects = getPageObjects([ 'common', @@ -47,8 +46,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adding a metric visualization', async function () { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); expect(originalPanelCount).to.eql(0); - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); await dashboardVisualizations.createAndEmbedMetric('Embedding Vis Test'); await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.metricValuesExist(['0']); @@ -59,8 +56,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adding a markdown', async function () { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); expect(originalPanelCount).to.eql(1); - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); await dashboardVisualizations.createAndEmbedMarkdown({ name: 'Embedding Markdown Test', markdown: 'Nice to meet you, markdown is my name', diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts index 4b9b2f99215b..1c7e8ceb28fb 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts @@ -40,6 +40,16 @@ export class LogStreamEmbeddableFactoryDefinition }); } + public getDescription() { + return i18n.translate('xpack.infra.logStreamEmbeddable.description', { + defaultMessage: 'Add a table of live streaming logs.', + }); + } + + public getIconType() { + return 'logsApp'; + } + public async getExplicitInput() { return { title: i18n.translate('xpack.infra.logStreamEmbeddable.title', { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts index ac5ff2094e22..4788d809f016 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { StartServicesAccessor } from 'kibana/public'; +import { PLUGIN_ICON, PLUGIN_ID, ML_APP_NAME } from '../../../common/constants/app'; import type { EmbeddableFactoryDefinition, IContainer, @@ -27,6 +28,14 @@ export class AnomalyChartsEmbeddableFactory implements EmbeddableFactoryDefinition { public readonly type = ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE; + public readonly grouping = [ + { + id: PLUGIN_ID, + getDisplayName: () => ML_APP_NAME, + getIconType: () => PLUGIN_ICON, + }, + ]; + constructor( private getStartServices: StartServicesAccessor ) {} @@ -37,7 +46,13 @@ export class AnomalyChartsEmbeddableFactory public getDisplayName() { return i18n.translate('xpack.ml.components.mlAnomalyExplorerEmbeddable.displayName', { - defaultMessage: 'ML anomaly chart', + defaultMessage: 'Anomaly chart', + }); + } + + public getDescription() { + return i18n.translate('xpack.ml.components.mlAnomalyExplorerEmbeddable.description', { + defaultMessage: 'View anomaly detection results in a chart.', }); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts index fdb2ef852792..bc45e075710c 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { StartServicesAccessor } from 'kibana/public'; +import { PLUGIN_ID, PLUGIN_ICON, ML_APP_NAME } from '../../../common/constants/app'; import type { EmbeddableFactoryDefinition, IContainer, @@ -26,6 +27,14 @@ export class AnomalySwimlaneEmbeddableFactory implements EmbeddableFactoryDefinition { public readonly type = ANOMALY_SWIMLANE_EMBEDDABLE_TYPE; + public readonly grouping = [ + { + id: PLUGIN_ID, + getDisplayName: () => ML_APP_NAME, + getIconType: () => PLUGIN_ICON, + }, + ]; + constructor( private getStartServices: StartServicesAccessor ) {} @@ -36,7 +45,13 @@ export class AnomalySwimlaneEmbeddableFactory public getDisplayName() { return i18n.translate('xpack.ml.components.jobAnomalyScoreEmbeddable.displayName', { - defaultMessage: 'ML anomaly swim lane', + defaultMessage: 'Anomaly swim lane', + }); + } + + public getDescription() { + return i18n.translate('xpack.ml.components.jobAnomalyScoreEmbeddable.description', { + defaultMessage: 'View anomaly detection results in a timeline.', }); } diff --git a/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts b/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts index deb91f6b9b1e..51875c683346 100644 --- a/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts +++ b/x-pack/test/accessibility/apps/ml_embeddables_in_dashboard.ts @@ -96,8 +96,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('can open job selection flyout', async () => { await PageObjects.dashboard.clickCreateDashboardPrompt(); await ml.dashboardEmbeddables.assertDashboardIsEmpty(); - await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.ensureAddPanelIsShowing(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickEmbeddableFactoryGroupButton('ml'); await dashboardAddPanel.clickAddNewEmbeddableLink('ml_anomaly_charts'); await ml.dashboardJobSelectionTable.assertJobSelectionTableExists(); await a11y.testAppSnapshot(); diff --git a/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts index 56a8ab46a57d..87ecfe0dcada 100644 --- a/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts @@ -15,7 +15,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardVisualizations = getService('dashboardVisualizations'); describe('dashboard lens by value', function () { before(async () => { @@ -27,7 +26,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('can add a lens panel by value', async () => { - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await PageObjects.lens.createAndAddLensFromDashboard({}); const newPanelCount = await PageObjects.dashboard.getPanelCount(); expect(newPanelCount).to.eql(1); diff --git a/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts index 15c76c3367a8..487dc90e1877 100644 --- a/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/dashboard_maps_by_value.ts @@ -19,10 +19,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const log = getService('log'); const esArchiver = getService('esArchiver'); - const dashboardVisualizations = getService('dashboardVisualizations'); const dashboardPanelActions = getService('dashboardPanelActions'); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); + const dashboardAddPanel = getService('dashboardAddPanel'); const LAYER_NAME = 'World Countries'; let mapCounter = 0; @@ -33,7 +33,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await PageObjects.visualize.clickMapsApp(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickVisType('maps'); await PageObjects.maps.clickSaveAndReturnButton(); } @@ -82,8 +83,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('adding a map by value', () => { it('can add a map by value', async () => { await createNewDashboard(); - - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await createAndAddMapByValue(); const newPanelCount = await PageObjects.dashboard.getPanelCount(); expect(newPanelCount).to.eql(1); @@ -93,7 +92,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('editing a map by value', () => { before(async () => { await createNewDashboard(); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await createAndAddMapByValue(); await editByValueMap(); }); @@ -112,7 +110,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('editing a map and adding to map library', () => { beforeEach(async () => { await createNewDashboard(); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await createAndAddMapByValue(); }); diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/time_to_visualize_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/time_to_visualize_security.ts index 3ebc53cc7cf2..730c00a8d5e4 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/time_to_visualize_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/time_to_visualize_security.ts @@ -21,7 +21,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'lens', ]); - const dashboardVisualizations = getService('dashboardVisualizations'); + const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardExpect = getService('dashboardExpect'); const testSubjects = getService('testSubjects'); @@ -85,7 +85,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('can add a lens panel by value', async () => { - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await PageObjects.lens.createAndAddLensFromDashboard({}); const newPanelCount = await PageObjects.dashboard.getPanelCount(); expect(newPanelCount).to.eql(1); @@ -171,9 +170,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.waitForRenderComplete(); - await testSubjects.click('dashboardAddNewPanelButton'); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickMarkdownWidget(); + await dashboardAddPanel.clickMarkdownQuickButton(); await PageObjects.visEditor.setMarkdownTxt(originalMarkdownText); await PageObjects.visEditor.clickGo(); diff --git a/x-pack/test/functional/apps/dashboard/sync_colors.ts b/x-pack/test/functional/apps/dashboard/sync_colors.ts index 7e54f966870c..09575c355913 100644 --- a/x-pack/test/functional/apps/dashboard/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/sync_colors.ts @@ -49,7 +49,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.dashboard.clickCreateDashboardPrompt(); await dashboardAddPanel.clickCreateNewLink(); - await dashboardAddPanel.clickVisType('lens'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.goToTimeRange(); @@ -68,7 +67,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.save('vis1', false, true); await PageObjects.header.waitUntilLoadingHasFinished(); await dashboardAddPanel.clickCreateNewLink(); - await dashboardAddPanel.clickVisType('lens'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.configureDimension({ diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js index 57925ad50d15..37311de53419 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js @@ -10,7 +10,6 @@ import expect from '@kbn/expect'; export default function ({ getPageObjects, getService }) { const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); - const dashboardVisualizations = getService('dashboardVisualizations'); const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']); @@ -29,9 +28,6 @@ export default function ({ getPageObjects, getService }) { it('adds Lens visualization to empty dashboard', async () => { const title = 'Dashboard Test Lens'; - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await PageObjects.lens.createAndAddLensFromDashboard({ title, redirectToOrigin: true }); await PageObjects.dashboard.waitForRenderComplete(); await testSubjects.exists(`embeddablePanelHeading-${title}`); @@ -87,9 +83,6 @@ export default function ({ getPageObjects, getService }) { const title = 'non-dashboard Test Lens'; await PageObjects.dashboard.loadSavedDashboard('empty dashboard test'); await PageObjects.dashboard.switchToEditMode(); - await testSubjects.exists('dashboardAddNewPanelButton'); - await testSubjects.click('dashboardAddNewPanelButton'); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); await PageObjects.lens.createAndAddLensFromDashboard({ title }); await PageObjects.lens.notLinkedToOriginatingApp(); await PageObjects.common.navigateToApp('dashboard'); diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts index a15176d76f95..1490abb320ca 100644 --- a/x-pack/test/functional/apps/lens/dashboard.ts +++ b/x-pack/test/functional/apps/lens/dashboard.ts @@ -134,7 +134,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await filterBar.addFilter('geo.dest', 'is', 'LS'); await dashboardAddPanel.clickCreateNewLink(); - await dashboardAddPanel.clickVisType('lens'); await PageObjects.header.waitUntilLoadingHasFinished(); const hasGeoDestFilter = await filterBar.hasFilter('geo.dest', 'LS'); expect(hasGeoDestFilter).to.be(false); @@ -200,7 +199,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickCreateNewLink(); - await dashboardAddPanel.clickVisType('lens'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.goToTimeRange(); diff --git a/x-pack/test/functional/apps/lens/lens_tagging.ts b/x-pack/test/functional/apps/lens/lens_tagging.ts index 7ce31709498f..6fff2baa2d0c 100644 --- a/x-pack/test/functional/apps/lens/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/lens_tagging.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const retry = getService('retry'); const find = getService('find'); - const dashboardVisualizations = getService('dashboardVisualizations'); + const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects([ 'common', @@ -39,8 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('adds a new tag to a Lens visualization', async () => { // create lens - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickLensWidget(); + await dashboardAddPanel.clickCreateNewLink(); await PageObjects.lens.goToTimeRange(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', diff --git a/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js b/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js index 40e73f0d8a76..9bff4e56c6c5 100644 --- a/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js +++ b/x-pack/test/functional/apps/maps/embeddable/embeddable_library.js @@ -15,7 +15,6 @@ export default function ({ getPageObjects, getService }) { const security = getService('security'); const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardVisualizations = getService('dashboardVisualizations'); describe('maps in embeddable library', () => { before(async () => { @@ -34,8 +33,7 @@ export default function ({ getPageObjects, getService }) { }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); - await dashboardAddPanel.clickCreateNewLink(); - await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); + await dashboardAddPanel.clickEditorMenuButton(); await PageObjects.visualize.clickMapsApp(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.maps.waitForLayersToLoad(); diff --git a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js index a3abb01b4cf9..a7e649548306 100644 --- a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js +++ b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js @@ -11,7 +11,6 @@ export default function ({ getPageObjects, getService }) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'maps', 'visualize']); const dashboardAddPanel = getService('dashboardAddPanel'); const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardVisualizations = getService('dashboardVisualizations'); const testSubjects = getService('testSubjects'); const security = getService('security'); @@ -37,9 +36,8 @@ export default function ({ getPageObjects, getService }) { beforeEach(async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.clickNewDashboard(); - await dashboardAddPanel.clickCreateNewLink(); - await await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); - await PageObjects.visualize.clickMapsApp(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickVisType('maps'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.maps.waitForLayersToLoad(); }); diff --git a/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts b/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts index f7bfd7f7a4c6..0aee183c1a4a 100644 --- a/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts +++ b/x-pack/test/functional/apps/ml/embeddables/anomaly_charts_dashboard_embeddables.ts @@ -87,8 +87,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('can open job selection flyout', async () => { await PageObjects.dashboard.clickCreateDashboardPrompt(); await ml.dashboardEmbeddables.assertDashboardIsEmpty(); - await dashboardAddPanel.clickOpenAddPanel(); - await dashboardAddPanel.ensureAddPanelIsShowing(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickEmbeddableFactoryGroupButton('ml'); await dashboardAddPanel.clickAddNewEmbeddableLink('ml_anomaly_charts'); await ml.dashboardJobSelectionTable.assertJobSelectionTableExists(); }); diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 65020be390f9..100ed8e079d3 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -18,6 +18,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont const find = getService('find'); const comboBox = getService('comboBox'); const browser = getService('browser'); + const dashboardAddPanel = getService('dashboardAddPanel'); const PageObjects = getPageObjects([ 'common', @@ -753,7 +754,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont if (inViewMode) { await PageObjects.dashboard.switchToEditMode(); } - await PageObjects.visualize.clickLensWidget(); + await dashboardAddPanel.clickCreateNewLink(); await this.goToTimeRange(); await this.configureDimension({ dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension',