diff --git a/src/platform/plugins/private/links/public/content_management/save_to_library.tsx b/src/platform/plugins/private/links/public/content_management/save_to_library.tsx index c58f2ce5db4aa..ce7cac1d2a949 100644 --- a/src/platform/plugins/private/links/public/content_management/save_to_library.tsx +++ b/src/platform/plugins/private/links/public/content_management/save_to_library.tsx @@ -12,8 +12,8 @@ import { i18n } from '@kbn/i18n'; import { showSaveModal, OnSaveProps, - SavedObjectSaveModal, SaveResult, + SavedObjectSaveModalWithSaveResult, } from '@kbn/saved-objects-plugin/public'; import { CONTENT_ID } from '../../common'; import { checkForDuplicateTitle } from './duplicate_title_check'; @@ -83,7 +83,7 @@ export const runSaveToLibrary = async ( }; const saveModal = ( - resolve(undefined)} title={newState.title ?? ''} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.test.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.test.tsx index 03d169c0a27ba..704b0d613049b 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.test.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.test.tsx @@ -15,7 +15,12 @@ import { BehaviorSubject } from 'rxjs'; jest.mock('@kbn/saved-objects-plugin/public', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { render } = require('@testing-library/react'); - const MockSavedObjectSaveModal = ({ onSave }: { onSave: (props: OnSaveProps) => void }) => { + const MockSavedObjectSaveModal = ({ + onSave, + }: { + onSave: (props: OnSaveProps) => Promise | unknown; + }) => { + // invoke onSave synchronously to simulate the user confirming the save onSave({ newTitle: 'Library panel one', newCopyOnSave: true, @@ -26,7 +31,7 @@ jest.mock('@kbn/saved-objects-plugin/public', () => { return null; }; return { - SavedObjectSaveModal: MockSavedObjectSaveModal, + SavedObjectSaveModalWithSaveResult: MockSavedObjectSaveModal, showSaveModal: (saveModal: React.ReactElement) => { render(saveModal); }, diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.tsx index 7f464289c36f1..a94daeb1a083d 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_actions/library_add_action.tsx @@ -30,7 +30,7 @@ import { import { OnSaveProps, SaveResult, - SavedObjectSaveModal, + SavedObjectSaveModalWithSaveResult, showSaveModal, } from '@kbn/saved-objects-plugin/public'; import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; @@ -113,7 +113,7 @@ export class AddToLibraryAction implements Action { } }; showSaveModal( - {}} title={lastTitle ?? ''} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.js.snap b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.tsx.snap similarity index 97% rename from src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.js.snap rename to src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.tsx.snap index d28208dfebc55..3f9c635c40575 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.js.snap +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/__snapshots__/save_modal.test.tsx.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders DashboardSaveModal 1`] = ` - diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.js b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.tsx similarity index 90% rename from src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.js rename to src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.tsx index 6f807d231650f..e88e472fcfb1e 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.js +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.test.tsx @@ -16,11 +16,14 @@ jest.mock('@kbn/saved-objects-plugin/public', () => ({ import { DashboardSaveModal } from './save_modal'; +const mockSave = jest.fn(); +const mockClose = jest.fn(); + test('renders DashboardSaveModal', () => { const component = shallowWithI18nProvider( {}} - onClose={() => {}} + onSave={mockSave} + onClose={mockClose} title="dash title" description="dash description" timeRestore={true} diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.tsx index 2f107cb4f37f9..0fa60c5b3a4ac 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/save_modal/save_modal.tsx @@ -12,7 +12,8 @@ import React, { Fragment, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { SavedObjectSaveModal } from '@kbn/saved-objects-plugin/public'; +import type { SaveResult } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectSaveModalWithSaveResult } from '@kbn/saved-objects-plugin/public'; import { savedObjectsTaggingService } from '../../services/kibana_services'; import type { DashboardSaveOptions } from './types'; @@ -25,7 +26,7 @@ interface DashboardSaveModalProps { newTimeRestore, isTitleDuplicateConfirmed, onTitleDuplicate, - }: DashboardSaveOptions) => void; + }: DashboardSaveOptions) => Promise; onClose: () => void; title: string; description: string; @@ -130,7 +131,7 @@ export const DashboardSaveModal: React.FC = ({ }, [persistSelectedTimeInterval, selectedTags, showStoreTimeOnSave]); return ( - void; - }) => { + }): Promise => { const appState = state.appState.getState(); const currentTitle = savedSearch.title; const currentTimeRestore = savedSearch.timeRestore; @@ -184,10 +191,10 @@ export async function onSaveSearch({ onSaveCb?.(); - return response; + return response ?? {}; }; - const saveModal = ( + const saveModal: React.ReactElement = ( void; + onSave: ( + props: OnSaveProps & { newTimeRestore: boolean; newTags: string[] } + ) => Promise; onClose: () => void; managed: boolean; }> = ({ @@ -237,8 +246,8 @@ const SaveSearchObjectModal: React.FC<{ ); const [currentTags, setCurrentTags] = useState(tags); - const onModalSave = (params: OnSaveProps) => { - onSave({ + const onModalSave = async (params: OnSaveProps): Promise => { + return onSave({ ...params, newTimeRestore: timeRestore, newTags: currentTags, @@ -287,7 +296,7 @@ const SaveSearchObjectModal: React.FC<{ ); return ( - import('./saved_object_save_modal_dashboard') ); +/** + * Used with `showSaveModal` to pass `SaveResult` back from `onSave` + */ +export const LazySavedObjectSaveModalDashboardWithSaveResult = React.lazy( + () => import('./saved_object_save_modal_dashboard_with_save_result') +); + export const LazyDataViewPicker = React.lazy(() => import('./data_view_picker/data_view_picker')); export const LazyFieldPicker = React.lazy(() => import('./field_picker/field_picker')); diff --git a/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard.tsx b/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard.tsx index e2e87649d4251..f55e0f711027d 100644 --- a/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard.tsx +++ b/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard.tsx @@ -21,7 +21,7 @@ import { SaveModalDashboardProps } from './types'; import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector'; import { getPresentationCapabilities } from '../utils/get_presentation_capabilities'; -function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { +function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { const { documentInfo, tagOptions, objectType, onClose, canSaveByReference } = props; const { id: documentId } = documentInfo; const initialCopyOnSave = !Boolean(documentId); @@ -75,7 +75,7 @@ function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { setCopyOnSave(newCopyOnSave); }; - const onModalSave = (onSaveProps: OnSaveProps) => { + const onModalSave = async (onSaveProps: OnSaveProps): Promise => { let dashboardId = null; // Don't save with a dashboard ID if we're @@ -88,7 +88,7 @@ function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { } } - props.onSave({ ...onSaveProps, dashboardId, addToLibrary: isAddToLibrarySelected }); + await props.onSave({ ...onSaveProps, dashboardId, addToLibrary: isAddToLibrarySelected }); }; const saveLibraryLabel = diff --git a/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard_with_save_result.tsx b/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard_with_save_result.tsx new file mode 100644 index 0000000000000..468baf2578635 --- /dev/null +++ b/src/platform/plugins/shared/presentation_util/public/components/saved_object_save_modal_dashboard_with_save_result.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 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 { SaveResult } from '@kbn/saved-objects-plugin/public'; + +import SavedObjectSaveModalDashboard from './saved_object_save_modal_dashboard'; + +/** + * Used with `showSaveModal` to pass `SaveResult` back from `onSave` + */ +const SavedObjectSaveModalDashboardWithSaveResult = SavedObjectSaveModalDashboard; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default SavedObjectSaveModalDashboardWithSaveResult; diff --git a/src/platform/plugins/shared/presentation_util/public/components/types.ts b/src/platform/plugins/shared/presentation_util/public/components/types.ts index 64574bd7d0e82..73d51f29c1b7a 100644 --- a/src/platform/plugins/shared/presentation_util/public/components/types.ts +++ b/src/platform/plugins/shared/presentation_util/public/components/types.ts @@ -21,12 +21,14 @@ interface SaveModalDocumentInfo { description?: string; } -export interface SaveModalDashboardProps { +export interface SaveModalDashboardProps { documentInfo: SaveModalDocumentInfo; canSaveByReference: boolean; objectType: string; onClose: () => void; - onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void; + onSave: ( + props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean } + ) => Promise; tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); // include a message if the user has to copy on save mustCopyOnSaveMessage?: string; diff --git a/src/platform/plugins/shared/presentation_util/public/index.ts b/src/platform/plugins/shared/presentation_util/public/index.ts index 67790ec49f02a..aff0fdc96ebe8 100644 --- a/src/platform/plugins/shared/presentation_util/public/index.ts +++ b/src/platform/plugins/shared/presentation_util/public/index.ts @@ -21,6 +21,7 @@ export { LazyLabsFlyout, LazyDashboardPicker, LazySavedObjectSaveModalDashboard, + LazySavedObjectSaveModalDashboardWithSaveResult, withSuspense, LazyDataViewPicker, LazyFieldPicker, diff --git a/src/platform/plugins/shared/saved_objects/public/index.ts b/src/platform/plugins/shared/saved_objects/public/index.ts index c0c8ae33f6b92..a26df06a10cfd 100644 --- a/src/platform/plugins/shared/saved_objects/public/index.ts +++ b/src/platform/plugins/shared/saved_objects/public/index.ts @@ -10,7 +10,13 @@ import { SavedObjectsPublicPlugin } from './plugin'; export type { OnSaveProps, OriginSaveModalProps, SaveModalState, SaveResult } from './save_modal'; -export { SavedObjectSaveModal, SavedObjectSaveModalOrigin, showSaveModal } from './save_modal'; +export { + SavedObjectSaveModal, + SavedObjectSaveModalWithSaveResult, + SavedObjectSaveModalOrigin, + showSaveModal, + type ShowSaveModalMinimalSaveModalProps, +} from './save_modal'; export { isErrorNonFatal } from './saved_object'; export type { SavedObjectSaveOpts, SavedObject, SavedObjectConfig } from './types'; diff --git a/src/platform/plugins/shared/saved_objects/public/save_modal/index.ts b/src/platform/plugins/shared/saved_objects/public/save_modal/index.ts index 878875f647b92..21c54ef23376f 100644 --- a/src/platform/plugins/shared/saved_objects/public/save_modal/index.ts +++ b/src/platform/plugins/shared/saved_objects/public/save_modal/index.ts @@ -8,8 +8,14 @@ */ export type { OnSaveProps, SaveModalState } from './saved_object_save_modal'; -export { SavedObjectSaveModal } from './saved_object_save_modal'; +export { + SavedObjectSaveModal, + SavedObjectSaveModalWithSaveResult, +} from './saved_object_save_modal'; export type { OriginSaveModalProps } from './saved_object_save_modal_origin'; export { SavedObjectSaveModalOrigin } from './saved_object_save_modal_origin'; export type { SaveResult } from './show_saved_object_save_modal'; -export { showSaveModal } from './show_saved_object_save_modal'; +export { + showSaveModal, + type ShowSaveModalMinimalSaveModalProps, +} from './show_saved_object_save_modal'; diff --git a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.test.tsx b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.test.tsx index 804e1a3083cbc..dfbab786392de 100644 --- a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.test.tsx +++ b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.test.tsx @@ -33,13 +33,16 @@ jest.mock('@elastic/eui', () => { }; }); +const mockSave = jest.fn(); +const mockClose = jest.fn(); + describe('SavedObjectSaveModal', () => { it('should render', async () => { const { findByTestId, getByText } = render( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -56,8 +59,8 @@ describe('SavedObjectSaveModal', () => { const { getByText } = render( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -75,8 +78,8 @@ describe('SavedObjectSaveModal', () => { const { getByText, rerender } = render( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -90,8 +93,8 @@ describe('SavedObjectSaveModal', () => { rerender( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -109,8 +112,8 @@ describe('SavedObjectSaveModal', () => { render( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -131,7 +134,7 @@ describe('SavedObjectSaveModal', () => { void 0} + onClose={mockClose} title={'Saved Object title'} objectType="visualization" showDescription={true} diff --git a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx index adc3b6f4533c6..a4393d6cbb59f 100644 --- a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -36,6 +36,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; +import type { SaveResult } from './show_saved_object_save_modal'; export interface OnSaveProps { newTitle: string; @@ -58,8 +59,8 @@ export interface SaveDashboardReturn { redirectRequired?: boolean; } -interface Props { - onSave: (props: OnSaveProps) => void; +interface Props { + onSave: (props: OnSaveProps) => Promise; onClose: () => void; title: string; showCopyOnSave: boolean; @@ -82,7 +83,7 @@ export interface SaveModalState { copyOnSave: boolean; isTitleDuplicateConfirmed: boolean; hasTitleDuplicate: boolean; - isLoading: boolean; + isSaving: boolean; visualizationDescription: string; hasAttemptedSubmit: boolean; } @@ -93,8 +94,8 @@ const generateId = htmlIdGenerator(); * @deprecated * @removeBy 8.8.0 */ -class SavedObjectSaveModalComponent extends React.Component< - Props, +class SavedObjectSaveModalComponent extends React.Component< + Props, SaveModalState, WithEuiThemeProps > { @@ -106,7 +107,7 @@ class SavedObjectSaveModalComponent extends React.Component< copyOnSave: Boolean(this.props.initialCopyOnSave), isTitleDuplicateConfirmed: false, hasTitleDuplicate: false, - isLoading: false, + isSaving: false, visualizationDescription: this.props.description ? this.props.description : '', hasAttemptedSubmit: false, }; @@ -258,7 +259,7 @@ class SavedObjectSaveModalComponent extends React.Component< private onTitleDuplicate = () => { this.setState({ - isLoading: false, + isSaving: false, isTitleDuplicateConfirmed: true, hasTitleDuplicate: true, }); @@ -269,18 +270,12 @@ class SavedObjectSaveModalComponent extends React.Component< }; private saveSavedObject = async () => { - if (this.state.isLoading) { - // ignore extra clicks - return; - } + if (this.state.isSaving) return; this.setState({ - isLoading: true, + isSaving: true, }); - // Although `onSave` is an asynchronous function, it is typed as returning `void` - // somewhere deeper in the call chain, which causes its asynchronous nature to be lost. - // We still need to treat it as async here to properly handle the loading state. try { await this.props.onSave({ newTitle: this.state.title, @@ -290,7 +285,7 @@ class SavedObjectSaveModalComponent extends React.Component< newDescription: this.state.visualizationDescription, }); } finally { - this.setState({ isLoading: false }); + this.setState({ isSaving: false }); } }; @@ -350,7 +345,7 @@ class SavedObjectSaveModalComponent extends React.Component< }; private renderConfirmButton = () => { - const { isLoading } = this.state; + const { isSaving } = this.state; let confirmLabel: string | React.ReactNode = i18n.translate( 'savedObjects.saveModal.saveButtonLabel', @@ -367,7 +362,7 @@ class SavedObjectSaveModalComponent extends React.Component< @@ -444,4 +439,19 @@ class SavedObjectSaveModalComponent extends React.Component< }; } -export const SavedObjectSaveModal = withEuiTheme(SavedObjectSaveModalComponent); +/** + * @deprecated + */ +export const SavedObjectSaveModal = withEuiTheme(SavedObjectSaveModalComponent); + +/** + * This is a workaround for using this directly with the `showSaveModal` method. + * + * The `showSaveModal` method wraps and calls these props from outside but this modal + * does not require the `SaveResult` to be returned from `onSave`. + * + * @deprecated + */ +export const SavedObjectSaveModalWithSaveResult = withEuiTheme( + SavedObjectSaveModalComponent +); diff --git a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx index 531224b0f3c9e..4512468b24aad 100644 --- a/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx +++ b/src/platform/plugins/shared/saved_objects/public/save_modal/saved_object_save_modal_origin.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { OnSaveProps, SaveModalState, SavedObjectSaveModal } from '.'; +import { OnSaveProps, SaveModalState, SaveResult, SavedObjectSaveModalWithSaveResult } from '.'; interface SaveModalDocumentInfo { id?: string; @@ -29,7 +29,7 @@ export interface OriginSaveModalProps { objectType: string; onClose: () => void; options?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); - onSave: (props: OnSaveProps & { returnToOrigin: boolean }) => void; + onSave: (props: OnSaveProps & { returnToOrigin: boolean }) => Promise; } export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { @@ -88,8 +88,8 @@ export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { } }; - const onModalSave = (onSaveProps: OnSaveProps) => { - props.onSave({ ...onSaveProps, returnToOrigin: returnToOriginMode }); + const onModalSave = async (onSaveProps: OnSaveProps): Promise => { + return props.onSave({ ...onSaveProps, returnToOrigin: returnToOriginMode }); }; const confirmButtonLabel = returnToOriginMode @@ -99,7 +99,7 @@ export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { : null; return ( - Promise; onClose: () => void; } +/** + * @deprecated legacy modal display mechanism + */ export function showSaveModal( - saveModal: React.ReactElement, + saveModal: React.ReactElement, Wrapper?: FC> ) { // initialize variable that will hold reference for unmount @@ -50,7 +56,7 @@ export function showSaveModal( const onSave = saveModal.props.onSave; - const onSaveConfirmed: MinimalSaveModalProps['onSave'] = async (...args) => { + const onSaveConfirmed: ShowSaveModalMinimalSaveModalProps['onSave'] = async (...args) => { const response = await onSave(...args); // close modal if we either hit an error or the saved object got an id if (Boolean(isSuccess(response) ? response.id : response.error)) { diff --git a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/attribute_service.tsx b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/attribute_service.tsx index 463cf08e00aed..54c7fd1b02c0f 100644 --- a/src/platform/plugins/shared/visualizations/public/legacy/embeddable/attribute_service.tsx +++ b/src/platform/plugins/shared/visualizations/public/legacy/embeddable/attribute_service.tsx @@ -10,10 +10,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { get, omit } from 'lodash'; +import { OnSaveProps, SaveResult } from '@kbn/saved-objects-plugin/public'; import { - SavedObjectSaveModal, - OnSaveProps, - SaveResult, + SavedObjectSaveModalWithSaveResult, showSaveModal, } from '@kbn/saved-objects-plugin/public'; import { getNotifications } from '../../services'; @@ -152,7 +151,7 @@ export class AttributeService { }; if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) { showSaveModal( - {}} title={get( diff --git a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 0b9ce28abf759..3e21187695599 100644 --- a/src/platform/plugins/shared/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/platform/plugins/shared/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -21,9 +21,11 @@ import { SavedObjectSaveModalOrigin, SavedObjectSaveOpts, OnSaveProps, + SaveResult, + ShowSaveModalMinimalSaveModalProps, } from '@kbn/saved-objects-plugin/public'; import { - LazySavedObjectSaveModalDashboard, + LazySavedObjectSaveModalDashboardWithSaveResult, withSuspense, } from '@kbn/presentation-util-plugin/public'; import { unhashUrl } from '@kbn/kibana-utils-plugin/public'; @@ -76,7 +78,9 @@ export interface TopNavConfigParams { eventEmitter?: EventEmitter; } -const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); +const SavedObjectSaveModalDashboardWithSaveResult = withSuspense( + LazySavedObjectSaveModalDashboardWithSaveResult +); export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { if (!anonymousUserCapabilities.visualize_v2) return false; @@ -584,7 +588,7 @@ export const getTopNavConfig = ( }: OnSaveProps & { returnToOrigin?: boolean } & { dashboardId?: string | null; addToLibrary?: boolean; - }) => { + }): Promise => { const currentTitle = savedVis.title; savedVis.title = newTitle; embeddableHandler.updateInput({ title: newTitle }); @@ -629,7 +633,7 @@ export const getTopNavConfig = ( }); // TODO: Saved Object Modal requires `id` to be defined so this is a workaround - return { id: true }; + return { id: 'true' }; } // We're adding the viz to a library so we need to save it and then @@ -659,7 +663,7 @@ export const getTopNavConfig = ( ); } - let saveModal; + let saveModal: React.ReactElement; if (originatingApp) { saveModal = ( @@ -690,7 +694,7 @@ export const getTopNavConfig = ( ); } else { saveModal = ( - void; + onSave: (props: OnSaveGraphProps) => Promise; onClose: () => void; title: string; description: string; @@ -37,9 +38,9 @@ export function SaveModal({ const [newDescription, setDescription] = useState(description); const [dataConsent, setDataConsent] = useState(false); return ( - { - onSave({ ...props, newDescription, dataConsent }); + { + return onSave({ ...props, newDescription, dataConsent }); }} onClose={onClose} title={title} diff --git a/x-pack/platform/plugins/private/graph/public/services/save_modal.tsx b/x-pack/platform/plugins/private/graph/public/services/save_modal.tsx index e05800ec20ddb..0ea4ba286df84 100644 --- a/x-pack/platform/plugins/private/graph/public/services/save_modal.tsx +++ b/x-pack/platform/plugins/private/graph/public/services/save_modal.tsx @@ -43,14 +43,14 @@ export function openSaveModal({ }) { const currentTitle = workspace.title; const currentDescription = workspace.description; - const onSave = ({ + const onSave = async ({ newTitle, newDescription, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate, dataConsent, - }: OnSaveGraphProps) => { + }: OnSaveGraphProps): Promise => { workspace.title = newTitle; workspace.description = newDescription; workspace.copyOnSave = newCopyOnSave; @@ -68,6 +68,7 @@ export function openSaveModal({ return response; }); }; + showSaveModal( = ({ timeRange, ]); - const onSaveCallback: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + const onSaveCallback = useCallback( + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput: Partial = { diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/attachments_menu.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/attachments_menu.tsx index aa937e2588c0f..ee10a15091162 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/attachments_menu.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_categorization/attachments_menu.tsx @@ -81,7 +81,7 @@ export const AttachmentsMenu = ({ const canEditDashboards = capabilities.dashboard_v2.createNew; const onSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput: Partial = { diff --git a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx index 155e121a1055b..c17705251f792 100644 --- a/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx +++ b/x-pack/platform/plugins/shared/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_attachments_menu.tsx @@ -80,7 +80,7 @@ export const LogRateAnalysisAttachmentsMenu = ({ const isCasesAttachmentEnabled = showLogRateAnalysisResults && significantItems.length > 0; const onSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput: Partial = { diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal.tsx index b1bab16ccb89b..a224c112621d6 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal.tsx @@ -37,7 +37,7 @@ export interface Props { returnToOriginSwitchLabel?: string; returnToOrigin?: boolean; onClose: () => void; - onSave: (props: SaveProps, options: { saveToLibrary: boolean }) => void; + onSave: (props: SaveProps, options: { saveToLibrary: boolean }) => Promise; managed: boolean; } @@ -89,9 +89,9 @@ export const SaveModal = (props: Props) => { savedObjectsTagging={savedObjectsTagging} initialTags={tagsIds} canSaveByReference={Boolean(savingToLibraryPermitted)} - onSave={(saveProps) => { + onSave={async (saveProps) => { const saveToLibrary = Boolean(saveProps.addToLibrary); - onSave(saveProps, { saveToLibrary }); + await onSave(saveProps, { saveToLibrary }); }} onClose={onClose} documentInfo={{ diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container.tsx index f3120c7b9ce22..4f5db27150484 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container.tsx @@ -174,8 +174,8 @@ export function SaveModalContainer({ savingToLibraryPermitted={savingToLibraryPermitted} savedObjectsTagging={savedObjectsTagging} tagsIds={tagsIds} - onSave={(saveProps, options) => { - runLensSave(saveProps, options); + onSave={async (saveProps, options) => { + await runLensSave(saveProps, options); }} onClose={onClose} getAppNameFromId={getAppNameFromId} diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx index 9f932d0b23fd8..52d7bcf38ec17 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx @@ -27,7 +27,7 @@ export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit< > & { initialTags: string[]; savedObjectsTagging?: SavedObjectTaggingPluginStart; - onSave: (props: DashboardSaveProps) => void; + onSave: (props: DashboardSaveProps) => Promise; getOriginatingPath?: (dashboardId: string) => string; }; @@ -52,9 +52,9 @@ export const TagEnhancedSavedObjectSaveModalDashboard: FC< const tagEnhancedOptions = <>{tagSelectorOption}; - const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback( - (saveOptions) => { - onSave({ + const tagEnhancedOnSave = useCallback( + async (saveOptions) => { + await onSave({ ...saveOptions, returnToOrigin: false, newTags: selectedTags, diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx index e689ab2b49ce1..dff3b6414eccf 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx @@ -19,7 +19,7 @@ export type OriginSaveProps = OnSaveProps & { returnToOrigin: boolean; newTags?: export type TagEnhancedSavedObjectSaveModalOriginProps = Omit & { initialTags: string[]; savedObjectsTagging?: SavedObjectTaggingPluginStart; - onSave: (props: OriginSaveProps) => void; + onSave: (props: OriginSaveProps) => Promise; }; export const TagEnhancedSavedObjectSaveModalOrigin: FC< @@ -56,11 +56,12 @@ export const TagEnhancedSavedObjectSaveModalOrigin: FC< ); const tagEnhancedOnSave: OriginSaveModalProps['onSave'] = useCallback( - (saveOptions) => { - onSave({ + async (saveOptions) => { + await onSave({ ...saveOptions, newTags: selectedTags, }); + return {}; // SaveResult return type not needed here }, [onSave, selectedTags] ); diff --git a/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx b/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx index 5b782efef5c95..6177d4ead8c0d 100644 --- a/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.test.tsx @@ -26,6 +26,8 @@ import { taggingApiMock } from '@kbn/saved-objects-tagging-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; +const mockSave = jest.fn(); + describe('annotation group save action', () => { describe('save modal', () => { const modalSaveArgs = { @@ -94,7 +96,7 @@ describe('annotation group save action', () => { const wrapper = shallowWithIntl( {}} + onSave={mockSave} savedObjectsTagging={savedObjectsTagging} title={title} description={description} diff --git a/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.tsx b/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.tsx index 4fbeb20c03c16..6591d173c9aa2 100644 --- a/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.tsx +++ b/x-pack/platform/plugins/shared/lens/public/visualizations/xy/annotations/actions/save_action.tsx @@ -48,7 +48,7 @@ export const SaveModal = ({ }: { domElement: HTMLDivElement; savedObjectsTagging: SavedObjectTaggingPluginStart | undefined; - onSave: (props: ModalOnSaveProps) => void; + onSave: (props: ModalOnSaveProps) => Promise; title: string; description: string; tags: string[]; @@ -60,7 +60,9 @@ export const SaveModal = ({ return ( onSave({ ...props, closeModal, newTags: selectedTags })} + onSave={async (props) => { + await onSave({ ...props, closeModal, newTags: selectedTags }); + }} onClose={closeModal} title={title} description={description} diff --git a/x-pack/platform/plugins/shared/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/platform/plugins/shared/maps/public/routes/map_page/top_nav_config.tsx index d9b7aec6612f3..1e411d58489c4 100644 --- a/x-pack/platform/plugins/shared/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/platform/plugins/shared/maps/public/routes/map_page/top_nav_config.tsx @@ -10,11 +10,13 @@ import { i18n } from '@kbn/i18n'; import { Adapters } from '@kbn/inspector-plugin/public'; import { SavedObjectSaveModalOrigin, + ShowSaveModalMinimalSaveModalProps, OnSaveProps, + SaveResult, showSaveModal, } from '@kbn/saved-objects-plugin/public'; import { - LazySavedObjectSaveModalDashboard, + LazySavedObjectSaveModalDashboardWithSaveResult, withSuspense, } from '@kbn/presentation-util-plugin/public'; import { ScopedHistory } from '@kbn/core/public'; @@ -29,7 +31,9 @@ import { MAP_EMBEDDABLE_NAME } from '../../../common/constants'; import { SavedMap } from './saved_map'; import { checkForDuplicateTitle } from '../../content_management'; -const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); +const SavedObjectSaveModalDashboardWithSaveResult = withSuspense( + LazySavedObjectSaveModalDashboardWithSaveResult +); export function getTopNavConfig({ savedMap, @@ -169,7 +173,7 @@ export function getTopNavConfig({ dashboardId?: string | null; addToLibrary: boolean; } - ) => { + ): Promise => { try { await checkForDuplicateTitle( { @@ -210,7 +214,7 @@ export function getTopNavConfig({ }), }; - let saveModal; + let saveModal: React.ReactElement; if (savedMap.hasOriginatingApp()) { saveModal = ( @@ -234,7 +238,7 @@ export function getTopNavConfig({ ); } else { saveModal = ( - = ({ ); const onSaveCallback: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput: Partial = { diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/platform/plugins/shared/ml/public/application/explorer/anomaly_timeline.tsx index 5405c1e572829..d5f15e411a157 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/anomaly_timeline.tsx @@ -387,7 +387,7 @@ export const AnomalyTimeline: FC = React.memo( }, []); const onSaveCallback: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { if (!selectedJobs) return; const stateTransfer = embeddable!.getStateTransfer(); diff --git a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_controls/timeseriesexplorer_controls.tsx b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_controls/timeseriesexplorer_controls.tsx index 59835d032d6aa..b500d3f867883 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_controls/timeseriesexplorer_controls.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_controls/timeseriesexplorer_controls.tsx @@ -178,7 +178,7 @@ export const TimeSeriesExplorerControls: FC = ({ } const onSaveCallback: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const config = getDefaultEmbeddablePanelConfig(selectedJobId); diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx index eb9d43aadc520..93a0c587a8376 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx @@ -43,7 +43,7 @@ export function ErrorBudgetChartPanel({ const { embeddable } = useKibana().services; const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput = { title: newTitle, diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/hooks/use_slo_list_actions.ts b/x-pack/solutions/observability/plugins/slo/public/pages/slos/hooks/use_slo_list_actions.ts index e8646e7b5b8ed..1ab175c4de265 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/hooks/use_slo_list_actions.ts +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/hooks/use_slo_list_actions.ts @@ -28,7 +28,7 @@ export function useSloListActions({ }; const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId, newTitle, newDescription }) => { + async ({ dashboardId, newTitle, newDescription }) => { const stateTransfer = embeddable!.getStateTransfer(); const embeddableInput = { title: newTitle, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/common/components/add_to_dashboard.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/common/components/add_to_dashboard.tsx index 07ca4d68aa5f6..aef177710d436 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/common/components/add_to_dashboard.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/common/components/add_to_dashboard.tsx @@ -59,7 +59,7 @@ export const useAddToDashboard = ({ const { embeddable } = useKibana().services; const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId }) => { + async ({ dashboardId }) => { const stateTransfer = embeddable.getStateTransfer(); const state = {