diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/index.ts b/src/platform/plugins/private/links/public/embeddable/embeddable_module.ts similarity index 75% rename from src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/index.ts rename to src/platform/plugins/private/links/public/embeddable/embeddable_module.ts index a72a3112aeba1..04dc3245ef73d 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/index.ts +++ b/src/platform/plugins/private/links/public/embeddable/embeddable_module.ts @@ -7,5 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { useGetDashboardPanels } from './use_get_dashboard_panels'; -export { DashboardPanelSelectionListFlyout } from './dashboard_panel_selection_flyout'; +export { getLinksEmbeddableFactory } from './links_embeddable'; +export { deserializeLinksSavedObject } from '../lib/deserialize_from_library'; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.test.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.test.ts deleted file mode 100644 index daf6a4fc036fd..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks'; -import { getAddPanelActionMenuItemsGroup } from './add_panel_action_menu_items'; - -describe('getAddPanelActionMenuItems', () => { - it('returns the items correctly', async () => { - const registeredActions = [ - { - id: 'ACTION_CREATE_ESQL_CHART', - type: 'ACTION_CREATE_ESQL_CHART', - getDisplayName: () => 'Action name', - getIconType: () => 'pencil', - getDisplayNameTooltip: () => 'Action tooltip', - isCompatible: () => Promise.resolve(true), - execute: jest.fn(), - }, - { - id: 'TEST_ACTION_01', - type: 'TEST_ACTION_01', - getDisplayName: () => 'Action name 01', - getIconType: () => 'pencil', - getDisplayNameTooltip: () => 'Action tooltip', - isCompatible: () => Promise.resolve(true), - execute: jest.fn(), - grouping: [ - { - id: 'groupedAddPanelAction', - getDisplayName: () => 'Custom group', - getIconType: () => 'logoElasticsearch', - }, - ], - }, - { - id: 'TEST_ACTION_02', - type: 'TEST_ACTION_02', - getDisplayName: () => 'Action name', - getDisplayNameTooltip: () => 'Action tooltip', - getIconType: () => undefined, - isCompatible: () => Promise.resolve(true), - execute: jest.fn(), - grouping: [ - { - id: 'groupedAddPanelAction', - getDisplayName: () => 'Custom group', - getIconType: () => 'logoElasticsearch', - }, - ], - }, - ]; - const grouped = getAddPanelActionMenuItemsGroup( - getMockPresentationContainer(), - registeredActions, - jest.fn() - ); - - expect(grouped).toStrictEqual({ - groupedAddPanelAction: { - id: 'groupedAddPanelAction', - title: 'Custom group', - order: 0, - 'data-test-subj': 'dashboardEditorMenu-groupedAddPanelActionGroup', - items: [ - { - 'data-test-subj': 'create-action-Action name 01', - icon: 'pencil', - id: 'TEST_ACTION_01', - name: 'Action name 01', - onClick: expect.any(Function), - description: 'Action tooltip', - order: 0, - }, - { - 'data-test-subj': 'create-action-Action name', - icon: 'empty', - id: 'TEST_ACTION_02', - name: 'Action name', - onClick: expect.any(Function), - description: 'Action tooltip', - order: 0, - }, - ], - }, - other: { - id: 'other', - title: 'Other', - order: -1, - 'data-test-subj': 'dashboardEditorMenu-otherGroup', - items: [ - { - id: 'ACTION_CREATE_ESQL_CHART', - name: 'Action name', - icon: 'pencil', - description: 'Action tooltip', - onClick: expect.any(Function), - 'data-test-subj': 'create-action-Action name', - order: 0, - }, - ], - }, - }); - }); - - it('returns empty array if no actions have been registered', async () => { - const grouped = getAddPanelActionMenuItemsGroup(getMockPresentationContainer(), [], jest.fn()); - - expect(grouped).toStrictEqual({}); - }); -}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.ts deleted file mode 100644 index fe1d1e73e5a96..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/add_panel_action_menu_items.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - type ActionExecutionContext, - type Action, - addPanelMenuTrigger, -} from '@kbn/ui-actions-plugin/public'; -import { PresentationContainer } from '@kbn/presentation-containers'; -import { ADD_PANEL_OTHER_GROUP } from '@kbn/embeddable-plugin/public'; -import type { IconType, CommonProps } from '@elastic/eui'; -import React, { type MouseEventHandler } from 'react'; - -export interface PanelSelectionMenuItem extends Pick { - id: string; - name: string; - icon: IconType; - onClick: MouseEventHandler; - description?: string; - isDisabled?: boolean; - isDeprecated?: boolean; - order: number; -} - -export type GroupedAddPanelActions = Pick< - PanelSelectionMenuItem, - 'id' | 'isDisabled' | 'data-test-subj' | 'order' -> & { - title: string; - items: PanelSelectionMenuItem[]; -}; - -const onAddPanelActionClick = - (action: Action, context: ActionExecutionContext, closePopover: () => void) => - (event: React.MouseEvent) => { - closePopover(); - if (event.currentTarget instanceof HTMLAnchorElement) { - if ( - !event.defaultPrevented && // onClick prevented default - event.button === 0 && - (!event.currentTarget.target || event.currentTarget.target === '_self') && - !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) - ) { - event.preventDefault(); - action.execute(context); - } - } else action.execute(context); - }; - -export const getAddPanelActionMenuItemsGroup = ( - api: PresentationContainer, - actions: Array> | undefined, - onPanelSelected: () => void -) => { - const grouped: Record = {}; - - const context = { - embeddable: api, - trigger: addPanelMenuTrigger, - }; - - const getMenuItem = (item: Action): PanelSelectionMenuItem => { - const actionName = item.getDisplayName(context); - - return { - id: item.id, - name: actionName, - icon: - (typeof item.getIconType === 'function' ? item.getIconType(context) : undefined) ?? 'empty', - onClick: onAddPanelActionClick(item, context, onPanelSelected), - 'data-test-subj': `create-action-${actionName}`, - description: item?.getDisplayNameTooltip?.(context), - order: item.order ?? 0, - }; - }; - - actions?.forEach((item) => { - if (Array.isArray(item.grouping)) { - item.grouping.forEach((group) => { - const groupId = group.id; - if (!grouped[groupId]) { - grouped[groupId] = { - id: groupId, - title: group.getDisplayName ? group.getDisplayName(context) : '', - 'data-test-subj': `dashboardEditorMenu-${groupId}Group`, - order: group.order ?? 0, - items: [], - }; - } - - grouped[group.id]!.items!.push(getMenuItem(item)); - }); - } else { - // use other group as the default for definitions that don't have a group - const fallbackGroup = ADD_PANEL_OTHER_GROUP; - - if (!grouped[fallbackGroup.id]) { - grouped[fallbackGroup.id] = { - id: fallbackGroup.id, - title: fallbackGroup.getDisplayName?.() || '', - 'data-test-subj': `dashboardEditorMenu-${fallbackGroup.id}Group`, - order: fallbackGroup.order || 0, - items: [], - }; - } - - grouped[fallbackGroup.id].items.push(getMenuItem(item)); - } - }); - - return grouped; -}; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.test.tsx deleted file mode 100644 index 20c7cb3a61bf8..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { type ComponentProps } from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { DashboardPanelSelectionListFlyout } from './dashboard_panel_selection_flyout'; -import type { GroupedAddPanelActions } from './add_panel_action_menu_items'; - -const defaultProps: Omit< - ComponentProps, - 'fetchDashboardPanels' -> = { - close: jest.fn(), - paddingSize: 's', -}; - -const renderComponent = ({ - fetchDashboardPanels, -}: Pick, 'fetchDashboardPanels'>) => - render( - - - - ); - -const panelConfiguration: GroupedAddPanelActions[] = [ - { - id: 'panel1', - title: 'App 1', - items: [ - { - icon: 'icon1', - id: 'mockFactory', - name: 'Factory 1', - description: 'Factory 1 description', - 'data-test-subj': 'createNew-mockFactory', - onClick: jest.fn(), - order: 0, - }, - ], - order: 10, - 'data-test-subj': 'dashboardEditorMenu-group1Group', - }, -]; - -describe('DashboardPanelSelectionListFlyout', () => { - it('renders a loading indicator when fetchDashboardPanel has not yielded any value', async () => { - const promiseDelay = 5000; - - renderComponent({ - fetchDashboardPanels: jest.fn( - () => - new Promise((resolve) => { - setTimeout(() => resolve(panelConfiguration), promiseDelay); - }) - ), - }); - - expect( - await screen.findByTestId('dashboardPanelSelectionLoadingIndicator') - ).toBeInTheDocument(); - }); - - it('renders an error indicator when fetchDashboardPanel errors', async () => { - renderComponent({ - fetchDashboardPanels: jest.fn().mockRejectedValue(new Error('simulated error')), - }); - - expect(await screen.findByTestId('dashboardPanelSelectionErrorIndicator')).toBeInTheDocument(); - }); - - it('renders the list of available panels when fetchDashboardPanel resolves a value', async () => { - renderComponent({ fetchDashboardPanels: jest.fn().mockResolvedValue(panelConfiguration) }); - - expect(await screen.findByTestId(panelConfiguration[0]['data-test-subj']!)).toBeInTheDocument(); - }); - - it('renders a not found message when a user searches for an item that is not in the selection list', async () => { - renderComponent({ fetchDashboardPanels: jest.fn().mockResolvedValue(panelConfiguration) }); - - expect(await screen.findByTestId(panelConfiguration[0]['data-test-subj']!)).toBeInTheDocument(); - - await userEvent.type( - screen.getByTestId('dashboardPanelSelectionFlyout__searchInput'), - 'non existent panel' - ); - - expect(await screen.findByTestId('dashboardPanelSelectionNoPanelMessage')).toBeInTheDocument(); - }); - - it('invokes the close method when the flyout close btn is clicked', async () => { - renderComponent({ fetchDashboardPanels: jest.fn().mockResolvedValue(panelConfiguration) }); - - fireEvent.click(await screen.findByTestId('dashboardPanelSelectionCloseBtn')); - - expect(defaultProps.close).toHaveBeenCalled(); - }); -}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx deleted file mode 100644 index e6adece8ab36d..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/dashboard_panel_selection_flyout.tsx +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useEffect, useState } from 'react'; -import { i18n as i18nFn } from '@kbn/i18n'; -import { type EuiFlyoutProps, EuiLoadingChart } from '@elastic/eui'; -import orderBy from 'lodash/orderBy'; -import { - EuiEmptyPrompt, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiForm, - EuiBadge, - EuiFormRow, - EuiTitle, - EuiFieldSearch, - useEuiTheme, - EuiListGroup, - EuiListGroupItem, - EuiToolTip, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - type PanelSelectionMenuItem, - type GroupedAddPanelActions, -} from './add_panel_action_menu_items'; - -export interface DashboardPanelSelectionListFlyoutProps { - /** Handler to close flyout */ - close: () => void; - /** Padding for flyout */ - paddingSize: Exclude; - /** Fetches the panels available for a dashboard */ - fetchDashboardPanels: () => Promise; -} - -export const DashboardPanelSelectionListFlyout: React.FC< - DashboardPanelSelectionListFlyoutProps -> = ({ close, paddingSize, fetchDashboardPanels }) => { - const { euiTheme } = useEuiTheme(); - const [{ data: panels, loading, error }, setPanelState] = useState<{ - loading: boolean; - data: GroupedAddPanelActions[] | null; - error: unknown | null; - }>({ loading: true, data: null, error: null }); - - const [searchTerm, setSearchTerm] = useState(''); - const [panelsSearchResult, setPanelsSearchResult] = useState( - panels - ); - - useEffect(() => { - const requestDashboardPanels = () => { - fetchDashboardPanels() - .then((_panels) => - setPanelState((prevState) => ({ - ...prevState, - loading: false, - data: _panels, - })) - ) - .catch((err) => - setPanelState((prevState) => ({ - ...prevState, - loading: false, - error: err, - })) - ); - }; - - requestDashboardPanels(); - }, [fetchDashboardPanels]); - - useEffect(() => { - const _panels = (panels ?? []).slice(0); - - if (!searchTerm) { - return setPanelsSearchResult(_panels); - } - - const q = searchTerm.toLowerCase(); - - setPanelsSearchResult( - orderBy( - _panels.map((panel) => { - const groupSearchMatch = panel.title.toLowerCase().includes(q); - - const [groupSearchMatchAgg, items] = panel.items.reduce( - (acc, cur) => { - const searchMatch = cur.name.toLowerCase().includes(q); - - acc[0] = acc[0] || searchMatch; - acc[1].push({ - ...cur, - isDisabled: !(groupSearchMatch || searchMatch), - }); - - return acc; - }, - [groupSearchMatch, [] as PanelSelectionMenuItem[]] - ); - - return { - ...panel, - isDisabled: !groupSearchMatchAgg, - items, - }; - }), - ['isDisabled'] - ) - ); - }, [panels, searchTerm]); - - return ( - <> - - -

- -

-
-
- - - - - - { - setSearchTerm(e.target.value); - }} - aria-label={i18nFn.translate( - 'dashboard.editorMenu.addPanelFlyout.searchLabelText', - { defaultMessage: 'search field for panels' } - )} - className="nsPanelSelectionFlyout__searchInput" - data-test-subj="dashboardPanelSelectionFlyout__searchInput" - /> - - - - - {loading ? ( - } - /> - ) : ( - - {panelsSearchResult?.some(({ isDisabled }) => !isDisabled) ? ( - panelsSearchResult.map( - ({ id, title, items, isDisabled, ['data-test-subj']: dataTestSubj, order }) => - !isDisabled ? ( - - - {typeof title === 'string' ?

{title}

: title} -
- - {items?.map((item, idx) => { - return ( - - {!item.isDeprecated ? ( - {item.name} - ) : ( - - - {item.name} - - - - - - - - )} - - } - onClick={item?.onClick} - iconType={item.icon} - data-test-subj={item['data-test-subj']} - isDisabled={item.isDisabled} - /> - ); - })} - -
- ) : null - ) - ) : ( - <> - {Boolean(error) ? ( - - - - } - data-test-subj="dashboardPanelSelectionErrorIndicator" - /> - ) : ( - - - - )} - - )} -
- )} -
-
-
- - - - - - - - - - - ); -}; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts deleted file mode 100644 index 7965403a6b01a..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { ADD_PANEL_ANNOTATION_GROUP, ADD_PANEL_LEGACY_GROUP } from '@kbn/embeddable-plugin/public'; -import type { PresentationContainer } from '@kbn/presentation-containers'; -import type { Action, UiActionsService } from '@kbn/ui-actions-plugin/public'; -import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public'; -import { - VisGroups, - VisTypeAlias, - VisualizationsStart, - type BaseVisType, -} from '@kbn/visualizations-plugin/public'; -import { renderHook } from '@testing-library/react'; - -import { uiActionsService, visualizationsService } from '../../../services/kibana_services'; -import { useGetDashboardPanels } from './use_get_dashboard_panels'; - -const mockApi = { addNewPanel: jest.fn() } as unknown as jest.Mocked; - -describe('Get dashboard panels hook', () => { - const defaultHookProps: Parameters[0] = { - api: mockApi, - createNewVisType: jest.fn(), - }; - - let compatibleTriggerActionsRequestSpy: jest.SpyInstance< - ReturnType> - >; - - let dashboardVisualizationGroupGetterSpy: jest.SpyInstance< - ReturnType - >; - - let dashboardVisualizationAliasesGetterSpy: jest.SpyInstance< - ReturnType - >; - - beforeAll(() => { - compatibleTriggerActionsRequestSpy = jest.spyOn( - uiActionsService, - 'getTriggerCompatibleActions' - ); - dashboardVisualizationGroupGetterSpy = jest.spyOn(visualizationsService, 'getByGroup'); - dashboardVisualizationAliasesGetterSpy = jest.spyOn(visualizationsService, 'getAliases'); - }); - - beforeEach(() => { - compatibleTriggerActionsRequestSpy.mockResolvedValue([]); - dashboardVisualizationGroupGetterSpy.mockReturnValue([]); - dashboardVisualizationAliasesGetterSpy.mockReturnValue([]); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - afterAll(() => { - jest.resetAllMocks(); - }); - - describe('useGetDashboardPanels', () => { - it('hook return value is callable', () => { - const { result } = renderHook(() => useGetDashboardPanels(defaultHookProps)); - expect(result.current).toBeInstanceOf(Function); - }); - - it('returns a callable method that yields a cached result if invoked after a prior resolution', async () => { - const { result } = renderHook(() => useGetDashboardPanels(defaultHookProps)); - expect(result.current).toBeInstanceOf(Function); - - const firstInvocationResult = await result.current(jest.fn()); - - expect(compatibleTriggerActionsRequestSpy).toHaveBeenCalledWith(ADD_PANEL_TRIGGER, { - embeddable: expect.objectContaining(mockApi), - }); - - const secondInvocationResult = await result.current(jest.fn()); - - expect(firstInvocationResult).toStrictEqual(secondInvocationResult); - - expect(compatibleTriggerActionsRequestSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('augmenting ui action group items with dashboard visualization types', () => { - it.each([ - ['visualizations', VisGroups.PROMOTED], - [ADD_PANEL_LEGACY_GROUP.id, VisGroups.LEGACY], - [ADD_PANEL_ANNOTATION_GROUP.id, VisGroups.TOOLS], - ])( - 'includes in the ui action %s group, %s dashboard visualization group types', - async (uiActionGroupId, dashboardVisualizationGroupId) => { - const mockVisualizationsUiAction: Action = { - id: `some-${uiActionGroupId}-action`, - type: '', - order: 10, - grouping: [ - { - id: uiActionGroupId, - order: 1000, - getDisplayName: jest.fn(), - getIconType: jest.fn(), - }, - ], - getDisplayName: jest.fn(() => `Some ${uiActionGroupId} visualization Action`), - getIconType: jest.fn(), - execute: jest.fn(), - isCompatible: jest.fn(() => Promise.resolve(true)), - }; - - const mockDashboardVisualizationType = { - name: dashboardVisualizationGroupId, - title: dashboardVisualizationGroupId, - order: 0, - description: `This is a dummy representation of a ${dashboardVisualizationGroupId} visualization.`, - icon: 'empty', - stage: 'production', - isDeprecated: false, - group: dashboardVisualizationGroupId, - titleInWizard: `Custom ${dashboardVisualizationGroupId} visualization`, - } as BaseVisType; - - compatibleTriggerActionsRequestSpy.mockResolvedValue([mockVisualizationsUiAction]); - - dashboardVisualizationGroupGetterSpy.mockImplementation((group) => { - if (group !== dashboardVisualizationGroupId) return []; - - return [mockDashboardVisualizationType]; - }); - - const { result } = renderHook(() => useGetDashboardPanels(defaultHookProps)); - expect(result.current).toBeInstanceOf(Function); - - expect(await result.current(jest.fn())).toStrictEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: uiActionGroupId, - 'data-test-subj': `dashboardEditorMenu-${uiActionGroupId}Group`, - items: expect.arrayContaining([ - expect.objectContaining({ - // @ts-expect-error ignore passing the required context in this test - 'data-test-subj': `create-action-${mockVisualizationsUiAction.getDisplayName()}`, - }), - expect.objectContaining({ - 'data-test-subj': `visType-${mockDashboardVisualizationType.name}`, - }), - ]), - }), - ]) - ); - } - ); - - it('includes in the ui action visualization group dashboard visualization alias types', async () => { - const mockVisualizationsUiAction: Action = { - id: 'some-vis-action', - type: '', - order: 10, - grouping: [ - { - id: 'visualizations', - order: 1000, - getDisplayName: jest.fn(), - getIconType: jest.fn(), - }, - ], - getDisplayName: jest.fn(() => 'Some visualization Action'), - getIconType: jest.fn(), - execute: jest.fn(), - isCompatible: jest.fn(() => Promise.resolve(true)), - }; - - const mockedAliasVisualizationType: VisTypeAlias = { - name: 'alias visualization', - title: 'Alias Visualization', - order: 0, - description: 'This is a dummy representation of aan aliased visualization.', - icon: 'empty', - stage: 'production', - isDeprecated: false, - }; - - compatibleTriggerActionsRequestSpy.mockResolvedValue([mockVisualizationsUiAction]); - - dashboardVisualizationAliasesGetterSpy.mockReturnValue([mockedAliasVisualizationType]); - - const { result } = renderHook(() => useGetDashboardPanels(defaultHookProps)); - expect(result.current).toBeInstanceOf(Function); - - expect(await result.current(jest.fn())).toStrictEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: mockVisualizationsUiAction.grouping![0].id, - 'data-test-subj': `dashboardEditorMenu-${ - mockVisualizationsUiAction.grouping![0].id - }Group`, - items: expect.arrayContaining([ - expect.objectContaining({ - // @ts-expect-error ignore passing the required context in this test - 'data-test-subj': `create-action-${mockVisualizationsUiAction.getDisplayName()}`, - }), - expect.objectContaining({ - 'data-test-subj': `visType-${mockedAliasVisualizationType.name}`, - }), - ]), - }), - ]) - ); - }); - }); -}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.ts deleted file mode 100644 index 8d26c2a3b5708..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { useCallback, useMemo, useRef } from 'react'; -import { AsyncSubject, defer, from, lastValueFrom, map, type Subscription } from 'rxjs'; - -import { ADD_PANEL_ANNOTATION_GROUP, ADD_PANEL_LEGACY_GROUP } from '@kbn/embeddable-plugin/public'; -import { PresentationContainer } from '@kbn/presentation-containers'; -import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public'; -import { VisGroups, type BaseVisType, type VisTypeAlias } from '@kbn/visualizations-plugin/public'; - -import { uiActionsService, visualizationsService } from '../../../services/kibana_services'; -import { - getAddPanelActionMenuItemsGroup, - type GroupedAddPanelActions, - type PanelSelectionMenuItem, -} from './add_panel_action_menu_items'; - -interface UseGetDashboardPanelsArgs { - api: PresentationContainer; - createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void; -} - -const sortGroupPanelsByOrder = (panelGroups: T[]): T[] => { - return panelGroups.sort( - // larger number sorted to the top - (panelGroupA, panelGroupB) => panelGroupB.order - panelGroupA.order - ); -}; - -export const useGetDashboardPanels = ({ api, createNewVisType }: UseGetDashboardPanelsArgs) => { - const panelsComputeResultCache = useRef(new AsyncSubject()); - const panelsComputeSubscription = useRef(null); - - const getSortedVisTypesByGroup = (group: VisGroups) => - visualizationsService - .getByGroup(group) - .sort((a: BaseVisType | VisTypeAlias, b: BaseVisType | VisTypeAlias) => { - const labelA = 'titleInWizard' in a ? a.titleInWizard || a.title : a.title; - const labelB = 'titleInWizard' in b ? b.titleInWizard || a.title : a.title; - if (labelA < labelB) { - return -1; - } - if (labelA > labelB) { - return 1; - } - return 0; - }) - .filter(({ disableCreate }: BaseVisType) => !disableCreate); - - const promotedVisTypes = getSortedVisTypesByGroup(VisGroups.PROMOTED); - const toolVisTypes = getSortedVisTypesByGroup(VisGroups.TOOLS); - const legacyVisTypes = getSortedVisTypesByGroup(VisGroups.LEGACY); - - const visTypeAliases = visualizationsService - .getAliases() - .sort(({ promotion: a = false }: VisTypeAlias, { promotion: b = false }: VisTypeAlias) => - a === b ? 0 : a ? -1 : 1 - ) - .filter(({ disableCreate }: VisTypeAlias) => !disableCreate); - - const augmentedCreateNewVisType = useCallback( - (visType: Parameters[0], cb: () => void) => { - const visClickHandler = createNewVisType(visType); - return () => { - visClickHandler(); - cb(); - }; - }, - [createNewVisType] - ); - - const getVisTypeMenuItem = useCallback( - (onClickCb: () => void, visType: BaseVisType): PanelSelectionMenuItem => { - const { - name, - title, - titleInWizard, - description, - icon = 'empty', - isDeprecated, - order, - } = visType; - return { - id: name, - name: titleInWizard || title, - isDeprecated, - icon, - onClick: augmentedCreateNewVisType(visType, onClickCb), - 'data-test-subj': `visType-${name}`, - description, - order, - }; - }, - [augmentedCreateNewVisType] - ); - - const getVisTypeAliasMenuItem = useCallback( - (onClickCb: () => void, visTypeAlias: VisTypeAlias): PanelSelectionMenuItem => { - const { name, title, description, icon = 'empty', order } = visTypeAlias; - - return { - id: name, - name: title, - icon, - onClick: augmentedCreateNewVisType(visTypeAlias, onClickCb), - 'data-test-subj': `visType-${name}`, - description, - order: order ?? 0, - }; - }, - [augmentedCreateNewVisType] - ); - - const addPanelAction$ = useMemo( - () => - defer(() => { - return from( - uiActionsService.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, { - embeddable: api, - }) ?? [] - ); - }), - [api] - ); - - const computeAvailablePanels = useCallback( - (onPanelSelected: () => void) => { - if (!panelsComputeSubscription.current) { - panelsComputeSubscription.current = addPanelAction$ - .pipe( - map((addPanelActions) => - getAddPanelActionMenuItemsGroup(api, addPanelActions, onPanelSelected) - ), - map((groupedAddPanelAction) => { - return sortGroupPanelsByOrder( - Object.values(groupedAddPanelAction) - ).map((panelGroup) => { - switch (panelGroup.id) { - case 'visualizations': { - return { - ...panelGroup, - items: sortGroupPanelsByOrder( - (panelGroup.items ?? []).concat( - // TODO: actually add grouping to vis type alias so we wouldn't randomly display an unintended item - visTypeAliases.map(getVisTypeAliasMenuItem.bind(null, onPanelSelected)), - promotedVisTypes.map(getVisTypeMenuItem.bind(null, onPanelSelected)) - ) - ), - }; - } - case ADD_PANEL_LEGACY_GROUP.id: { - return { - ...panelGroup, - items: sortGroupPanelsByOrder( - (panelGroup.items ?? []).concat( - legacyVisTypes.map(getVisTypeMenuItem.bind(null, onPanelSelected)) - ) - ), - }; - } - case ADD_PANEL_ANNOTATION_GROUP.id: { - return { - ...panelGroup, - items: sortGroupPanelsByOrder( - (panelGroup.items ?? []).concat( - toolVisTypes.map(getVisTypeMenuItem.bind(null, onPanelSelected)) - ) - ), - }; - } - default: { - return { - ...panelGroup, - items: sortGroupPanelsByOrder(panelGroup.items), - }; - } - } - }); - }) - ) - .subscribe(panelsComputeResultCache.current); - } - }, - [ - api, - addPanelAction$, - getVisTypeMenuItem, - getVisTypeAliasMenuItem, - toolVisTypes, - legacyVisTypes, - promotedVisTypes, - visTypeAliases, - ] - ); - - return useCallback( - (...args: Parameters) => { - computeAvailablePanels(...args); - return lastValueFrom(panelsComputeResultCache.current.asObservable()); - }, - [computeAvailablePanels] - ); -}; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_button.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_button.tsx new file mode 100644 index 0000000000000..d5e384864539c --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_button.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useEffect, useCallback } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { ToolbarButton } from '@kbn/shared-ux-button-toolbar'; + +import { AddPanelFlyout } from './add_panel_flyout'; +import { useDashboardApi } from '../../../../dashboard_api/use_dashboard_api'; +import { coreServices } from '../../../../services/kibana_services'; + +export const AddPanelButton = ({ isDisabled }: { isDisabled?: boolean }) => { + const dashboardApi = useDashboardApi(); + + useEffect(() => { + // ensure opened overlays are closed if a navigation event happens + return () => { + dashboardApi.clearOverlays(); + }; + }, [dashboardApi]); + + const openFlyout = useCallback(() => { + const overlayRef = coreServices.overlays.openFlyout( + toMountPoint( + React.createElement(function () { + return ; + }), + coreServices + ), + { + size: 'm', + maxWidth: 500, + paddingSize: 'm', + 'aria-labelledby': 'addPanelsFlyout', + 'data-test-subj': 'dashboardPanelSelectionFlyout', + onClose() { + dashboardApi.clearOverlays(); + overlayRef.close(); + }, + } + ); + + dashboardApi.openOverlay(overlayRef); + }, [dashboardApi]); + + return ( + + ); +}; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.test.tsx new file mode 100644 index 0000000000000..0691fd602bf7e --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { AddPanelFlyout } from './add_panel_flyout'; +import { DashboardApi } from '../../../../dashboard_api/types'; + +jest.mock('../get_menu_item_groups', () => ({})); + +const mockDashboardApi = {} as unknown as DashboardApi; + +describe('AddPanelFlyout', () => { + describe('getMenuItemGroups throws', () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('../get_menu_item_groups').getMenuItemGroups = async () => { + throw new Error('simulated getMenuItemGroups error'); + }; + }); + + test('displays getMenuItemGroups error', async () => { + render( + + + + ); + + await waitFor(() => { + screen.getByTestId('dashboardPanelSelectionErrorIndicator'); + }); + }); + }); + + describe('getMenuItemGroups returns results', () => { + const onClickMock = jest.fn(); + beforeEach(() => { + onClickMock.mockClear(); + // eslint-disable-next-line @typescript-eslint/no-var-requires + require('../get_menu_item_groups').getMenuItemGroups = async () => [ + { + id: 'panel1', + title: 'App 1', + items: [ + { + icon: 'icon1', + id: 'mockFactory', + name: 'Factory 1', + description: 'Factory 1 description', + 'data-test-subj': 'myItem', + onClick: onClickMock, + order: 0, + }, + ], + order: 10, + 'data-test-subj': 'dashboardEditorMenu-group1Group', + }, + ]; + }); + + test('calls item onClick handler when item is clicked', async () => { + render( + + + + ); + + await waitFor(async () => { + await userEvent.click(screen.getByTestId('myItem')); + expect(onClickMock).toBeCalled(); + }); + }); + + test('displays not found message when a user searches for an item that is not in the selection list', async () => { + render( + + + + ); + + await waitFor(async () => { + await userEvent.type( + screen.getByTestId('dashboardPanelSelectionFlyout__searchInput'), + 'non existent panel' + ); + screen.getByTestId('dashboardPanelSelectionNoPanelMessage'); + }); + }); + }); +}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.tsx new file mode 100644 index 0000000000000..4607cd35d9e0e --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/add_panel_flyout.tsx @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useEffect, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { i18n as i18nFn } from '@kbn/i18n'; +import { + EuiEmptyPrompt, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiForm, + EuiFormRow, + EuiTitle, + EuiFieldSearch, + useEuiTheme, + EuiSkeletonText, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DashboardApi } from '../../../../dashboard_api/types'; +import { MenuItem, MenuItemGroup } from '../types'; +import { getMenuItemGroups } from '../get_menu_item_groups'; +import { Groups } from './groups'; + +export function AddPanelFlyout({ dashboardApi }: { dashboardApi: DashboardApi }) { + const { euiTheme } = useEuiTheme(); + + const { + value: groups, + loading, + error, + } = useAsync(async () => { + return await getMenuItemGroups(dashboardApi); + }, [dashboardApi]); + + const [searchTerm, setSearchTerm] = useState(''); + const [filteredGroups, setFilteredGroups] = useState([]); + useEffect(() => { + if (!searchTerm) { + return setFilteredGroups(groups ?? []); + } + + const q = searchTerm.toLowerCase(); + + const currentGroups = groups ?? ([] as MenuItemGroup[]); + setFilteredGroups( + currentGroups + .map((group) => { + const groupMatch = group.title.toLowerCase().includes(q); + + const [itemsMatch, items] = group.items.reduce( + (acc, item) => { + const itemMatch = item.name.toLowerCase().includes(q); + + acc[0] = acc[0] || itemMatch; + acc[1].push({ + ...item, + isDisabled: !(groupMatch || itemMatch), + }); + + return acc; + }, + [false, [] as MenuItem[]] + ); + + return { + ...group, + isDisabled: !(groupMatch || itemsMatch), + items, + }; + }) + .filter((group) => !group.isDisabled) + ); + }, [groups, searchTerm]); + + return ( + <> + + +

+ +

+
+
+ + + + + + + { + setSearchTerm(e.target.value); + }} + aria-label={i18nFn.translate( + 'dashboard.editorMenu.addPanelFlyout.searchLabelText', + { defaultMessage: 'search field for panels' } + )} + data-test-subj="dashboardPanelSelectionFlyout__searchInput" + /> + + + + + {error ? ( + + + + } + data-test-subj="dashboardPanelSelectionErrorIndicator" + /> + ) : ( + + )} + + + + + + + + + + + + + + + ); +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/group.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/group.tsx new file mode 100644 index 0000000000000..ea31731d9336a --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/group.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiListGroup, + EuiListGroupItem, + EuiText, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { MenuItemGroup } from '../types'; + +export function Group({ group }: { group: MenuItemGroup }) { + return ( + <> + +

{group.title}

+
+ + {group.items.map((item) => { + return ( + + {!item.isDeprecated ? ( + {item.name} + ) : ( + + + {item.name} + + + + + + + + )} + + } + onClick={item.onClick} + iconType={item.icon} + data-test-subj={item['data-test-subj']} + isDisabled={item.isDisabled} + /> + ); + })} + + + ); +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/groups.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/groups.tsx new file mode 100644 index 0000000000000..71ec9cc6c0359 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/components/groups.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 + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { Group } from './group'; +import { MenuItemGroup } from '../types'; + +export function Groups({ groups }: { groups: MenuItemGroup[] }) { + return groups.length === 0 ? ( + + + + ) : ( + + {groups.map((group) => ( + + + + ))} + + ); +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.test.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.test.ts new file mode 100644 index 0000000000000..9bccfba23c817 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getMenuItemGroups } from './get_menu_item_groups'; + +jest.mock('../../../services/kibana_services', () => ({ + uiActionsService: { + getTriggerCompatibleActions: async () => [ + { + id: 'mockAddPanelAction', + type: '', + order: 10, + grouping: [ + { + id: 'myGroup', + order: 900, + getDisplayName: () => 'My group', + }, + ], + getDisplayName: () => 'mockAddPanelAction', + getIconType: () => 'empty', + execute: () => {}, + isCompatible: async () => true, + }, + ], + }, + visualizationsService: { + all: () => [ + { + name: 'myPromotedVis', + title: 'myPromotedVis', + order: 0, + description: 'myPromotedVis description', + icon: 'empty', + stage: 'production', + isDeprecated: false, + group: 'promoted', + titleInWizard: 'myPromotedVis title', + }, + ], + getAliases: () => [ + { + name: 'alias visualization', + title: 'Alias Visualization', + order: 0, + description: 'This is a dummy representation of aan aliased visualization.', + icon: 'empty', + stage: 'production', + isDeprecated: false, + }, + ], + }, +})); + +describe('getMenuItemGroups', () => { + test('gets sorted groups from visTypes, visTypeAliases, and add panel actions', async () => { + const api = { + getAppContext: () => ({ + currentAppId: 'dashboards', + }), + openOverlay: () => {}, + clearOverlays: () => {}, + }; + const groups = await getMenuItemGroups(api); + expect(groups.length).toBe(2); + + expect(groups[0].title).toBe('Visualizations'); + expect(groups[0].items.length).toBe(2); // promoted vis type and vis alias + + expect(groups[1].title).toBe('My group'); + expect(groups[1].items.length).toBe(1); // add panel action + }); +}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.ts new file mode 100644 index 0000000000000..00e262c6b6f64 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/get_menu_item_groups.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { VisGroups } from '@kbn/visualizations-plugin/public'; +import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public'; +import { + ADD_PANEL_ANNOTATION_GROUP, + ADD_PANEL_LEGACY_GROUP, + ADD_PANEL_OTHER_GROUP, + ADD_PANEL_VISUALIZATION_GROUP, +} from '@kbn/embeddable-plugin/public'; +import type { TracksOverlays } from '@kbn/presentation-containers'; +import { PresentableGroup } from '@kbn/ui-actions-browser/src/types'; +import { addPanelMenuTrigger } from '@kbn/ui-actions-plugin/public'; +import type { HasAppContext } from '@kbn/presentation-publishing'; +import { uiActionsService, visualizationsService } from '../../../services/kibana_services'; +import { navigateToVisEditor } from './navigate_to_vis_editor'; +import type { MenuItem, MenuItemGroup } from './types'; + +const VIS_GROUP_TO_ADD_PANEL_GROUP: Record = { + [VisGroups.AGGBASED]: undefined, + [VisGroups.PROMOTED]: ADD_PANEL_VISUALIZATION_GROUP, + [VisGroups.TOOLS]: ADD_PANEL_ANNOTATION_GROUP, + [VisGroups.LEGACY]: ADD_PANEL_LEGACY_GROUP, +}; + +export async function getMenuItemGroups( + api: HasAppContext & TracksOverlays +): Promise { + const groups: Record = {}; + const addPanelContext = { + embeddable: api, + trigger: addPanelMenuTrigger, + }; + function pushItem(group: PresentableGroup, item: MenuItem) { + if (!groups[group.id]) { + groups[group.id] = { + id: group.id, + title: group.getDisplayName?.(addPanelContext) ?? '', + 'data-test-subj': `dashboardEditorMenu-${group.id}Group`, + order: group.order ?? 0, + items: [], + }; + } + groups[group.id].items.push(item); + } + + // add menu items from vis types + visualizationsService.all().forEach((visType) => { + if (visType.disableCreate) return; + + const group = VIS_GROUP_TO_ADD_PANEL_GROUP[visType.group]; + if (!group) return; + pushItem(group, { + id: visType.name, + name: visType.titleInWizard || visType.title, + isDeprecated: visType.isDeprecated, + icon: visType.icon ?? 'empty', + onClick: () => { + api.clearOverlays(); + navigateToVisEditor(api, visType); + }, + 'data-test-subj': `visType-${visType.name}`, + description: visType.description, + order: visType.order, + }); + }); + + // add menu items from vis alias + visualizationsService.getAliases().forEach((visTypeAlias) => { + if (visTypeAlias.disableCreate) return; + pushItem(ADD_PANEL_VISUALIZATION_GROUP, { + id: visTypeAlias.name, + name: visTypeAlias.title, + icon: visTypeAlias.icon ?? 'empty', + onClick: () => { + api.clearOverlays(); + navigateToVisEditor(api, visTypeAlias); + }, + 'data-test-subj': `visType-${visTypeAlias.name}`, + description: visTypeAlias.description, + order: visTypeAlias.order ?? 0, + }); + }); + + // add menu items from "add panel" actions + ( + await uiActionsService.getTriggerCompatibleActions(ADD_PANEL_TRIGGER, { embeddable: api }) + ).forEach((action) => { + const actionGroups = Array.isArray(action.grouping) ? action.grouping : [ADD_PANEL_OTHER_GROUP]; + actionGroups.forEach((group) => { + const actionName = action.getDisplayName(addPanelContext); + pushItem(group, { + id: action.id, + name: actionName, + icon: action.getIconType?.(addPanelContext) ?? 'empty', + onClick: (event: React.MouseEvent) => { + api.clearOverlays(); + if (event.currentTarget instanceof HTMLAnchorElement) { + if ( + !event.defaultPrevented && // onClick prevented default + event.button === 0 && + (!event.currentTarget.target || event.currentTarget.target === '_self') && + !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) + ) { + event.preventDefault(); + } + } + action.execute(addPanelContext); + }, + 'data-test-subj': `create-action-${actionName}`, + description: action?.getDisplayNameTooltip?.(addPanelContext), + order: action.order ?? 0, + }); + }); + }); + + return Object.values(groups) + .map((group) => { + group.items.sort((itemA, itemB) => { + return itemA.order === itemB.order + ? itemA.name.localeCompare(itemB.name) + : itemB.order - itemA.order; + }); + return group; + }) + .sort((groupA, groupB) => groupB.order - groupA.order); +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/navigate_to_vis_editor.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/navigate_to_vis_editor.ts new file mode 100644 index 0000000000000..352a567f51777 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/navigate_to_vis_editor.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { HasAppContext } from '@kbn/presentation-publishing'; +import type { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { + dataService, + embeddableService, + usageCollectionService, +} from '../../../services/kibana_services'; +import { DASHBOARD_UI_METRIC_ID } from '../../../utils/telemetry_constants'; + +export function navigateToVisEditor(api: HasAppContext, visType?: BaseVisType | VisTypeAlias) { + let path = ''; + let appId = ''; + + if (visType) { + const trackUiMetric = usageCollectionService?.reportUiCounter.bind( + usageCollectionService, + DASHBOARD_UI_METRIC_ID + ); + if (trackUiMetric) { + trackUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`); + } + + if (!('alias' in visType)) { + // this visualization is not an alias + appId = 'visualize'; + path = `#/create?type=${encodeURIComponent(visType.name)}`; + } else if (visType.alias && 'path' in visType.alias) { + // this visualization **is** an alias, and it has an app to redirect to for creation + appId = visType.alias.app; + path = visType.alias.path; + } + } else { + appId = 'visualize'; + path = '#/create?'; + } + + const stateTransferService = embeddableService.getStateTransfer(); + stateTransferService.navigateToEditor(appId, { + path, + state: { + originatingApp: api.getAppContext()?.currentAppId, + originatingPath: api.getAppContext()?.getCurrentPath?.(), + searchSessionId: dataService.search.session.getSessionId(), + }, + }); +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/types.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/types.ts new file mode 100644 index 0000000000000..b43b453d3ba49 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/add_panel_button/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { MouseEventHandler } from 'react'; +import type { IconType, CommonProps } from '@elastic/eui'; + +export interface MenuItem extends Pick { + id: string; + name: string; + icon: IconType; + onClick: MouseEventHandler; + description?: string; + isDisabled?: boolean; + isDeprecated?: boolean; + order: number; +} + +export interface MenuItemGroup extends Pick { + id: string; + isDisabled?: boolean; + title: string; + order: number; + items: MenuItem[]; +} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx index fd5e947a33baa..cf8ec4ee6d3f4 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx @@ -9,80 +9,32 @@ import { useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -import { METRIC_TYPE } from '@kbn/analytics'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar'; -import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; import { useDashboardApi } from '../../dashboard_api/use_dashboard_api'; -import { DASHBOARD_UI_METRIC_ID } from '../../utils/telemetry_constants'; -import { - dataService, - embeddableService, - usageCollectionService, - visualizationsService, -} from '../../services/kibana_services'; +import { visualizationsService } from '../../services/kibana_services'; import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings'; import { ControlsToolbarButton } from './controls_toolbar_button'; -import { EditorMenu } from './editor_menu'; +import { AddPanelButton } from './add_panel_button/components/add_panel_button'; import { addFromLibrary } from '../../dashboard_container/embeddable/api'; +import { navigateToVisEditor } from './add_panel_button/navigate_to_vis_editor'; export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) { const { euiTheme } = useEuiTheme(); const dashboardApi = useDashboardApi(); - const lensAlias = useMemo( - () => visualizationsService.getAliases().find(({ name }) => name === 'lens'), - [] - ); - - const createNewVisType = useCallback( - (visType?: BaseVisType | VisTypeAlias) => () => { - let path = ''; - let appId = ''; - - if (visType) { - const trackUiMetric = usageCollectionService?.reportUiCounter.bind( - usageCollectionService, - DASHBOARD_UI_METRIC_ID - ); - if (trackUiMetric) { - trackUiMetric(METRIC_TYPE.CLICK, `${visType.name}:create`); - } - - if (!('alias' in visType)) { - // this visualization is not an alias - appId = 'visualize'; - path = `#/create?type=${encodeURIComponent(visType.name)}`; - } else if (visType.alias && 'path' in visType.alias) { - // this visualization **is** an alias, and it has an app to redirect to for creation - appId = visType.alias.app; - path = visType.alias.path; - } - } else { - appId = 'visualize'; - path = '#/create?'; - } - - const stateTransferService = embeddableService.getStateTransfer(); - stateTransferService.navigateToEditor(appId, { - path, - state: { - originatingApp: dashboardApi.getAppContext()?.currentAppId, - originatingPath: dashboardApi.getAppContext()?.getCurrentPath?.(), - searchSessionId: dataService.search.session.getSessionId(), - }, - }); - }, - [dashboardApi] - ); + const navigateToDefaultEditor = useCallback(() => { + const lensAlias = visualizationsService.getAliases().find(({ name }) => name === 'lens'); + navigateToVisEditor(dashboardApi, lensAlias); + }, [dashboardApi]); const controlGroupApi = useStateFromPublishingSubject(dashboardApi.controlGroupApi$); const extraButtons = [ - , + , addFromLibrary(dashboardApi)} size="s" @@ -106,7 +58,7 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean } isDisabled={isDisabled} iconType="lensApp" size="s" - onClick={createNewVisType(lensAlias)} + onClick={navigateToDefaultEditor} label={getCreateVisualizationButtonTitle()} data-test-subj="dashboardAddNewPanelButton" /> diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.scss b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.scss deleted file mode 100644 index 4b1a1d9b27d57..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.scss +++ /dev/null @@ -1,6 +0,0 @@ -.dshSolutionToolbar__editorContextMenu { - max-height: 60vh; - overflow-y: scroll; - @include euiScrollBar; - @include euiOverflowShadow; -} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.test.tsx deleted file mode 100644 index f82ad60929f24..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { render } from '@testing-library/react'; -import React from 'react'; -import { buildMockDashboardApi } from '../../mocks'; -import { EditorMenu } from './editor_menu'; - -import { DashboardContext } from '../../dashboard_api/use_dashboard_api'; -import { uiActionsService, visualizationsService } from '../../services/kibana_services'; - -jest.spyOn(uiActionsService, 'getTriggerCompatibleActions').mockResolvedValue([]); -jest.spyOn(visualizationsService, 'getByGroup').mockReturnValue([]); -jest.spyOn(visualizationsService, 'getAliases').mockReturnValue([]); - -describe('editor menu', () => { - it('renders without crashing', async () => { - const { api } = buildMockDashboardApi(); - render(, { - wrapper: ({ children }) => { - return {children}; - }, - }); - }); -}); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.tsx deleted file mode 100644 index 6d052225d6fba..0000000000000 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/top_nav/editor_menu.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import './editor_menu.scss'; - -import React, { useEffect, useCallback, type ComponentProps } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/react-kibana-mount'; -import { ToolbarButton } from '@kbn/shared-ux-button-toolbar'; - -import { useGetDashboardPanels, DashboardPanelSelectionListFlyout } from './add_new_panel'; -import { useDashboardApi } from '../../dashboard_api/use_dashboard_api'; -import { coreServices } from '../../services/kibana_services'; - -interface EditorMenuProps - extends Pick[0], 'createNewVisType'> { - isDisabled?: boolean; -} - -export const EditorMenu = ({ createNewVisType, isDisabled }: EditorMenuProps) => { - const dashboardApi = useDashboardApi(); - - const fetchDashboardPanels = useGetDashboardPanels({ - api: dashboardApi, - createNewVisType, - }); - - useEffect(() => { - // ensure opened dashboard is closed if a navigation event happens; - return () => { - dashboardApi.clearOverlays(); - }; - }, [dashboardApi]); - - const openDashboardPanelSelectionFlyout = useCallback( - function openDashboardPanelSelectionFlyout() { - const flyoutPanelPaddingSize: ComponentProps< - typeof DashboardPanelSelectionListFlyout - >['paddingSize'] = 'm'; - - const mount = toMountPoint( - React.createElement(function () { - return ( - - ); - }), - coreServices - ); - - dashboardApi.openOverlay( - coreServices.overlays.openFlyout(mount, { - size: 'm', - maxWidth: 500, - paddingSize: flyoutPanelPaddingSize, - 'aria-labelledby': 'addPanelsFlyout', - 'data-test-subj': 'dashboardPanelSelectionFlyout', - onClose(overlayRef) { - dashboardApi.clearOverlays(); - overlayRef.close(); - }, - }) - ); - }, - [dashboardApi, fetchDashboardPanels] - ); - - return ( - - ); -}; diff --git a/src/platform/plugins/shared/dashboard/tsconfig.json b/src/platform/plugins/shared/dashboard/tsconfig.json index e1bcc5fae059f..bae41ad9f78ed 100644 --- a/src/platform/plugins/shared/dashboard/tsconfig.json +++ b/src/platform/plugins/shared/dashboard/tsconfig.json @@ -85,7 +85,8 @@ "@kbn/core-rendering-browser", "@kbn/esql-variables-types", "@kbn/grid-layout", - "@kbn/esql-validation-autocomplete" + "@kbn/esql-validation-autocomplete", + "@kbn/ui-actions-browser" ], "exclude": ["target/**/*"] } diff --git a/src/platform/plugins/shared/embeddable/public/index.ts b/src/platform/plugins/shared/embeddable/public/index.ts index 31da8691af17e..c98e73dc76b47 100644 --- a/src/platform/plugins/shared/embeddable/public/index.ts +++ b/src/platform/plugins/shared/embeddable/public/index.ts @@ -57,4 +57,5 @@ export { ADD_PANEL_ANNOTATION_GROUP, ADD_PANEL_OTHER_GROUP, ADD_PANEL_LEGACY_GROUP, + ADD_PANEL_VISUALIZATION_GROUP, } from './ui_actions/add_panel_groups'; diff --git a/src/platform/plugins/shared/embeddable/public/ui_actions/add_panel_groups.ts b/src/platform/plugins/shared/embeddable/public/ui_actions/add_panel_groups.ts index d1111bb5d5a5f..75310d8ff42a4 100644 --- a/src/platform/plugins/shared/embeddable/public/ui_actions/add_panel_groups.ts +++ b/src/platform/plugins/shared/embeddable/public/ui_actions/add_panel_groups.ts @@ -9,6 +9,18 @@ import { i18n } from '@kbn/i18n'; +export const ADD_PANEL_VISUALIZATION_GROUP = { + id: 'visualizations', + getDisplayName: () => + i18n.translate('embeddableApi.common.constants.grouping.visualizations', { + defaultMessage: 'Visualizations', + }), + getIconType: () => { + return 'visGauge'; + }, + order: 1000, +}; + export const ADD_PANEL_ANNOTATION_GROUP = { id: 'annotation-and-navigation', getDisplayName: () => diff --git a/src/platform/plugins/shared/visualizations/public/index.ts b/src/platform/plugins/shared/visualizations/public/index.ts index 58b30592ee29e..a24ed48f73de3 100644 --- a/src/platform/plugins/shared/visualizations/public/index.ts +++ b/src/platform/plugins/shared/visualizations/public/index.ts @@ -20,7 +20,6 @@ export function plugin(initializerContext: PluginInitializerContext) { export { TypesService } from './vis_types/types_service'; export { VIS_EVENT_TO_TRIGGER } from './embeddable/events'; export { apiHasVisualizeConfig } from './embeddable/interfaces/has_visualize_config'; -export { COMMON_VISUALIZATION_GROUPING } from './legacy/embeddable/constants'; export { VisualizationContainer } from './components'; export { getVisSchemas } from './vis_schemas'; export { prepareLogTable } from '../common/utils/prepare_log_table'; diff --git a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/constants.ts b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/constants.ts deleted file mode 100644 index 79d87ec59b1e1..0000000000000 --- a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { i18n } from '@kbn/i18n'; - -export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../common/constants'; - -export const COMMON_VISUALIZATION_GROUPING = [ - { - id: 'visualizations', - getDisplayName: () => - i18n.translate('visualizations.common.constants.grouping.legacy', { - defaultMessage: 'Visualizations', - }), - getIconType: () => { - return 'visGauge'; - }, - order: 1000, - }, -]; diff --git a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/index.ts b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/index.ts index 6aa08f7b847de..025708758f4df 100644 --- a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/index.ts +++ b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/index.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { VISUALIZE_EMBEDDABLE_TYPE, COMMON_VISUALIZATION_GROUPING } from './constants'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../common/constants'; export { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object'; export type { VisualizeEmbeddable, VisualizeInput } from './visualize_embeddable'; diff --git a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/visualize_embeddable.tsx b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/visualize_embeddable.tsx index 3f77ce9ecb661..dc27834bb109d 100644 --- a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/visualize_embeddable.tsx +++ b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/visualize_embeddable.tsx @@ -38,7 +38,7 @@ import { StartServicesGetter } from '@kbn/kibana-utils-plugin/public'; import { isFallbackDataView } from '../../visualize_app/utils'; import { VisualizationMissedSavedObjectError } from '../../components/visualization_missed_saved_object_error'; import VisualizationError from '../../components/visualization_error'; -import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../common/constants'; import { SerializedVis, Vis } from '../../vis'; import { getApplication, getExpressions, getUiActions } from '../../services'; import { VIS_EVENT_TO_TRIGGER } from '../../embeddable/events'; diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 9527ba681f0a2..d7bb8e680e339 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -9531,7 +9531,6 @@ "visualizations.badge.readOnly.text": "Lecture seule", "visualizations.badge.readOnly.tooltip": "Impossible d'enregistrer les visualisations dans la bibliothèque", "visualizations.byValue_pageHeading": "Visualisation de type {chartType} intégrée à l'application {originatingApp}", - "visualizations.common.constants.grouping.legacy": "Visualisations", "visualizations.confirmModal.cancelButtonLabel": "Annuler", "visualizations.confirmModal.confirmTextDescription": "Quitter l'éditeur Visualize sans enregistrer les modifications ?", "visualizations.confirmModal.overwriteButtonLabel": "Écraser", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index f951b53f9b2bd..e49af049a1670 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -9406,7 +9406,6 @@ "visualizations.badge.readOnly.text": "読み取り専用", "visualizations.badge.readOnly.tooltip": "ビジュアライゼーションをライブラリに保存できません", "visualizations.byValue_pageHeading": "{originatingApp}アプリに埋め込まれた{chartType}タイプのビジュアライゼーション", - "visualizations.common.constants.grouping.legacy": "ビジュアライゼーション", "visualizations.confirmModal.cancelButtonLabel": "キャンセル", "visualizations.confirmModal.confirmTextDescription": "変更を保存せずにVisualizeエディターから移動しますか?", "visualizations.confirmModal.overwriteButtonLabel": "上書き", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 10ccac6ffb6da..dbfbd9a428be0 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -9265,7 +9265,6 @@ "visualizations.badge.readOnly.text": "只读", "visualizations.badge.readOnly.tooltip": "无法将可视化保存到库", "visualizations.byValue_pageHeading": "已嵌入到 {originatingApp} 应用中的 {chartType} 类型可视化", - "visualizations.common.constants.grouping.legacy": "可视化", "visualizations.confirmModal.cancelButtonLabel": "取消", "visualizations.confirmModal.confirmTextDescription": "离开 Visualize 编辑器而不保存更改?", "visualizations.confirmModal.overwriteButtonLabel": "覆盖", diff --git a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_lens_config/create_action.tsx b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_lens_config/create_action.tsx index 6fb9310158082..d59e2b629ef92 100644 --- a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_lens_config/create_action.tsx +++ b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_lens_config/create_action.tsx @@ -9,7 +9,7 @@ import type { CoreStart } from '@kbn/core/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { EmbeddableApiContext } from '@kbn/presentation-publishing'; import { apiIsPresentationContainer } from '@kbn/presentation-containers'; -import { COMMON_VISUALIZATION_GROUPING } from '@kbn/visualizations-plugin/public'; +import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public'; import type { LensPluginStartDependencies } from '../../plugin'; import type { EditorFrameService } from '../../editor_frame_service'; @@ -22,7 +22,7 @@ export class CreateESQLPanelAction implements Action { public id = ACTION_CREATE_ESQL_CHART; public order = 50; - public grouping = COMMON_VISUALIZATION_GROUPING; + public grouping = [ADD_PANEL_VISUALIZATION_GROUP]; constructor( protected readonly startDependencies: LensPluginStartDependencies,