diff --git a/src/plugins/dashboard/public/dashboard_api/get_dashboard_api.ts b/src/plugins/dashboard/public/dashboard_api/get_dashboard_api.ts index 6d4b2376f8900..de78b6b55166d 100644 --- a/src/plugins/dashboard/public/dashboard_api/get_dashboard_api.ts +++ b/src/plugins/dashboard/public/dashboard_api/get_dashboard_api.ts @@ -171,7 +171,7 @@ export function getDashboardApi({ unifiedSearchManager.internalApi.controlGroupReload$, unifiedSearchManager.internalApi.panelsReload$ ).pipe(debounceTime(0)), - getDashboardState: async () => { + getSerializedState: async () => { const { controlGroupReferences, dashboardState, panelReferences } = await getState(); return getDashboardContentManagementService().getDashboardState({ controlGroupReferences, diff --git a/src/plugins/dashboard/public/dashboard_api/types.ts b/src/plugins/dashboard/public/dashboard_api/types.ts index 6ae56b8a940cc..72a322be04a98 100644 --- a/src/plugins/dashboard/public/dashboard_api/types.ts +++ b/src/plugins/dashboard/public/dashboard_api/types.ts @@ -154,7 +154,7 @@ export type DashboardApi = CanExpandPanels & focusedPanelId$: PublishingSubject; forceRefresh: () => void; getSettings: () => DashboardStateFromSettingsFlyout; - getDashboardState: () => Promise<{ + getSerializedState: () => Promise<{ attributes: DashboardAttributes; references: SavedObjectReference[]; }>; diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index 8efcadaadca82..45fc421e4b900 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -182,14 +182,6 @@ export const topNavStrings = { defaultMessage: 'Switch to edit mode', }), }, - export: { - label: i18n.translate('dashboard.topNav.exportButtonAriaLabel', { - defaultMessage: 'export', - }), - description: i18n.translate('dashboard.topNav.exportConfigDescription', { - defaultMessage: 'Export Dashboard', - }), - }, quickSave: { label: i18n.translate('dashboard.topNave.saveButtonAriaLabel', { defaultMessage: 'save', diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/export/show_export_flyout.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/export/show_export_flyout.tsx deleted file mode 100644 index 1b7b7edab9d0c..0000000000000 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/export/show_export_flyout.tsx +++ /dev/null @@ -1,111 +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 } from 'react'; -import { css } from '@emotion/react'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { - EuiCallOut, - EuiCodeBlock, - EuiEmptyPrompt, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiLoadingChart, - EuiTitle, -} from '@elastic/eui'; -import { DashboardApi } from '../../..'; - -const flyoutBodyCss = css` - height: 100%; - .euiFlyoutBody__overflowContent { - height: 100%; - padding: 0; - } -`; - -export interface DashboardExportFlyoutProps { - close: () => void; - getDashboardState: DashboardApi['getDashboardState']; -} - -export const DashboardExportFlyout: React.FC = ({ - close, - getDashboardState, -}) => { - const [{ data: dashboard, loading, error }, setDashboardState] = React.useState<{ - loading: boolean; - data: Awaited> | null; - error: unknown | null; - }>({ loading: true, data: null, error: null }); - - useEffect(() => { - const loadDashboardState = () => { - getDashboardState() - .then((_dashboard) => - setDashboardState((prevState) => ({ - ...prevState, - loading: false, - data: _dashboard, - })) - ) - .catch((err) => - setDashboardState((prevState) => ({ - ...prevState, - loading: false, - error: err, - })) - ); - }; - - loadDashboardState(); - }, [getDashboardState]); - - return ( - <> - - -

- -

-
-
- - {error ? ( - - } - color="danger" - iconType="alert" - /> - ) : ( - <> - {loading ? ( - } - /> - ) : ( - - {JSON.stringify(dashboard, null, 2)} - - )} - - )} - - - ); -}; diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx index 37c3b1d01f125..07e29db545e7f 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx @@ -7,14 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react'; +import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; import useMountedState from 'react-use/lib/useMountedState'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import { toMountPoint } from '@kbn/react-kibana-mount'; import { UI_SETTINGS } from '../../../common'; import { useDashboardApi } from '../../dashboard_api/use_dashboard_api'; import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants'; @@ -26,7 +25,6 @@ import { coreServices, shareService } from '../../services/kibana_services'; import { getDashboardCapabilities } from '../../utils/get_dashboard_capabilities'; import { topNavStrings } from '../_dashboard_app_strings'; import { ShowShareModal } from './share/show_share_modal'; -import { DashboardExportFlyout } from './export/show_export_flyout'; export const useDashboardMenuItems = ({ isLabsShown, @@ -71,34 +69,6 @@ export const useDashboardMenuItems = ({ [dashboardTitle, hasUnsavedChanges, lastSavedId, dashboardApi] ); - /** - * Show Export flyout - */ - const showExport = useCallback(() => { - dashboardApi.openOverlay( - coreServices.overlays.openFlyout( - toMountPoint( - dashboardApi.clearOverlays()} - />, - { - i18n: coreServices.i18n, - theme: coreServices.theme, - } - ), - { - size: 'm', - 'data-test-subj': 'dashboardExportFlyout', - onClose: (flyout) => { - dashboardApi.clearOverlays(); - flyout.close(); - }, - } - ) - ); - }, [dashboardApi]); - /** * Save the dashboard without any UI or popups. */ @@ -229,14 +199,6 @@ export const useDashboardMenuItems = ({ run: showShare, } as TopNavMenuData, - export: { - ...topNavStrings.export, - id: 'export', - testId: 'dashboardExportMenuItem', - disableButton: disableTopNav, - run: showExport, - }, - settings: { ...topNavStrings.settings, id: 'settings', @@ -253,7 +215,6 @@ export const useDashboardMenuItems = ({ dashboardInteractiveSave, viewMode, showShare, - showExport, dashboardApi, setIsLabsShown, isLabsShown, @@ -303,7 +264,6 @@ export const useDashboardMenuItems = ({ ...labsMenuItem, menuItems.fullScreen, ...shareMenuItem, - menuItems.export, ...duplicateMenuItem, ...mayberesetChangesMenuItem, ...editMenuItem, @@ -326,13 +286,7 @@ export const useDashboardMenuItems = ({ } else { editModeItems.push(menuItems.switchToViewMode, menuItems.interactiveSave); } - return [ - ...labsMenuItem, - menuItems.settings, - ...shareMenuItem, - menuItems.export, - ...editModeItems, - ]; + return [...labsMenuItem, menuItems.settings, ...shareMenuItem, ...editModeItems]; }, [isLabsEnabled, menuItems, lastSavedId, showResetChange, resetChangesMenuItem]); return { viewModeTopNavConfig, editModeTopNavConfig }; diff --git a/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/get_dashboard_state.test.ts b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/get_dashboard_state.test.ts new file mode 100644 index 0000000000000..18bee11084b2b --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management_service/lib/get_dashboard_state.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { dataService, embeddableService, savedObjectsTaggingService } from '../../kibana_services'; +import { getSampleDashboardState } from '../../../mocks'; +import { DashboardState } from '../../../dashboard_api/types'; +import { getDashboardState } from './get_dashboard_state'; + +dataService.search.searchSource.create = jest.fn().mockResolvedValue({ + setField: jest.fn(), + getSerializedFields: jest.fn().mockReturnValue({}), +}); + +dataService.query.timefilter.timefilter.getTime = jest + .fn() + .mockReturnValue({ from: 'now-15m', to: 'now' }); + +dataService.query.timefilter.timefilter.getRefreshInterval = jest + .fn() + .mockReturnValue({ pause: true, value: 0 }); + +embeddableService.extract = jest + .fn() + .mockImplementation((attributes) => ({ state: attributes, references: [] })); + +if (savedObjectsTaggingService) { + savedObjectsTaggingService.getTaggingApi = jest.fn().mockReturnValue({ + ui: { + updateTagsReferences: jest.fn((references, tags) => references), + }, + }); +} + +describe('getDashboardState', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return the current state attributes and references', async () => { + const currentState = getSampleDashboardState(); + const result = await getDashboardState({ + controlGroupReferences: [], + generateNewIds: false, + currentState, + panelReferences: [], + }); + + expect(result.attributes.panels).toEqual([]); + expect(result.references).toEqual([]); + }); + + it('should generate new IDs for panels and references when generateNewIds is true', async () => { + const currentState = { + ...getSampleDashboardState(), + panels: { oldPanelId: { type: 'visualization' } }, + } as unknown as DashboardState; + const result = await getDashboardState({ + controlGroupReferences: [], + generateNewIds: true, + currentState, + panelReferences: [ + { + name: 'oldPanelId:indexpattern_foobar', + type: 'index-pattern', + id: 'bizzbuzz', + }, + ], + }); + + expect(result.attributes.panels).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + panelIndex: expect.not.stringMatching('oldPanelId'), + type: 'visualization', + }), + ]) + ); + expect(result.references).toEqual( + expect.arrayContaining([ + { + name: expect.not.stringMatching(/^oldPanelId:/), + id: 'bizzbuzz', + type: 'index-pattern', + }, + ]) + ); + }); + + it('should include control group references', async () => { + const currentState = getSampleDashboardState(); + const controlGroupReferences = [ + { name: 'control1:indexpattern', type: 'index-pattern', id: 'foobar' }, + ]; + const result = await getDashboardState({ + controlGroupReferences, + generateNewIds: false, + currentState, + panelReferences: [], + }); + + expect(result.references).toEqual(controlGroupReferences); + }); + + it('should include panel references', async () => { + const currentState = getSampleDashboardState(); + const panelReferences = [ + { name: 'panel1:boogiewoogie', type: 'index-pattern', id: 'fizzbuzz' }, + ]; + const result = await getDashboardState({ + controlGroupReferences: [], + generateNewIds: false, + currentState, + panelReferences, + }); + + expect(result.references).toEqual(panelReferences); + }); +});