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..1d912c953649a 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,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { showSaveModal, OnSaveProps, - SavedObjectSaveModal, + SavedObjectSaveModalWithSaveResult, SaveResult, } from '@kbn/saved-objects-plugin/public'; import { CONTENT_ID } from '../../common'; @@ -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; @@ -189,10 +196,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; }> = ({ @@ -242,8 +251,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, @@ -292,7 +301,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/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/platform/plugins/shared/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap index 9a26b9bfecc0e..97fd97e9de728 100644 --- a/src/platform/plugins/shared/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/platform/plugins/shared/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -4,7 +4,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` @@ -88,7 +88,7 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` > @@ -202,7 +202,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali > @@ -316,7 +316,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali > @@ -444,7 +444,7 @@ exports[`SavedObjectSaveModal should render matching snapshot when given options > { it('should render matching snapshot', () => { const wrapper = shallowWithIntl( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -34,8 +37,8 @@ describe('SavedObjectSaveModal', () => { it('should render matching snapshot when given options', () => { const wrapper = shallowWithIntl( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -50,8 +53,8 @@ describe('SavedObjectSaveModal', () => { it('should render matching snapshot when custom isValid is set', () => { const falseWrapper = shallowWithIntl( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -63,8 +66,8 @@ describe('SavedObjectSaveModal', () => { const trueWrapper = shallowWithIntl( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -81,8 +84,8 @@ describe('SavedObjectSaveModal', () => { render( void 0} - onClose={() => void 0} + onSave={mockSave} + onClose={mockClose} title={'Saved Object title'} showCopyOnSave={false} objectType="visualization" @@ -103,7 +106,7 @@ describe('SavedObjectSaveModal', () => { void 0} + onClose={mockClose} title={'Saved Object title'} objectType="visualization" showDescription={true} @@ -133,7 +136,7 @@ describe('SavedObjectSaveModal', () => { {}} + onClose={mockClose} title="Saved Object" objectType="visualization" showDescription={true} @@ -157,7 +160,7 @@ describe('SavedObjectSaveModal', () => { {}} + onClose={mockClose} title="Saved Object [1]" 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 8b502e75dd971..980a3d667cee7 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 @@ -33,6 +33,7 @@ import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { euiThemeVars } from '@kbn/ui-theme'; +import type { SaveResult } from './show_saved_object_save_modal'; export interface OnSaveProps { newTitle: string; @@ -55,8 +56,8 @@ export interface SaveDashboardReturn { redirectRequired?: boolean; } -interface Props { - onSave: (props: OnSaveProps) => void; +interface Props { + onSave: (props: OnSaveProps) => Promise; onClose: () => void; title: string; showCopyOnSave: boolean; @@ -78,7 +79,7 @@ export interface SaveModalState { copyOnSave: boolean; isTitleDuplicateConfirmed: boolean; hasTitleDuplicate: boolean; - isLoading: boolean; + isSaving: boolean; visualizationDescription: string; hasAttemptedSubmit: boolean; } @@ -89,7 +90,7 @@ const generateId = htmlIdGenerator(); * @deprecated * @removeBy 8.8.0 */ -export class SavedObjectSaveModal extends React.Component { +class SavedObjectSaveModalComponent extends React.Component, SaveModalState> { private warning = React.createRef(); private formId = generateId('form'); private savedObjectTitleInputRef = React.createRef(); @@ -99,7 +100,7 @@ export class SavedObjectSaveModal 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, }; @@ -252,7 +253,7 @@ export class SavedObjectSaveModal extends React.Component private onTitleDuplicate = () => { this.setState({ - isLoading: false, + isSaving: false, isTitleDuplicateConfirmed: true, hasTitleDuplicate: true, }); @@ -263,18 +264,12 @@ export class SavedObjectSaveModal 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, @@ -284,7 +279,7 @@ export class SavedObjectSaveModal extends React.Component newDescription: this.state.visualizationDescription, }); } finally { - this.setState({ isLoading: false }); + this.setState({ isSaving: false }); } }; @@ -344,7 +339,7 @@ export class SavedObjectSaveModal extends React.Component }; private renderConfirmButton = () => { - const { isLoading } = this.state; + const { isSaving } = this.state; let confirmLabel: string | React.ReactNode = i18n.translate( 'savedObjects.saveModal.saveButtonLabel', @@ -361,7 +356,7 @@ export class SavedObjectSaveModal extends React.Component @@ -434,3 +429,18 @@ export class SavedObjectSaveModal extends React.Component ); }; } + +/** + * @deprecated + */ +export const SavedObjectSaveModal = 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 = 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..a6c9a88932f02 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 @@ -11,10 +11,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { get, omit } from 'lodash'; import { - SavedObjectSaveModal, OnSaveProps, SaveResult, showSaveModal, + SavedObjectSaveModalWithSaveResult, } from '@kbn/saved-objects-plugin/public'; import { getNotifications } from '../../services'; import { @@ -152,7 +152,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 91714c37ec0a2..2a971d39a9034 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'; @@ -77,7 +79,9 @@ export interface TopNavConfigParams { eventEmitter?: EventEmitter; } -const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); +const SavedObjectSaveModalDashboardWithSaveResult = withSuspense( + LazySavedObjectSaveModalDashboardWithSaveResult +); export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { if (!anonymousUserCapabilities.visualize) return false; @@ -585,7 +589,7 @@ export const getTopNavConfig = ( }: OnSaveProps & { returnToOrigin?: boolean } & { dashboardId?: string | null; addToLibrary?: boolean; - }) => { + }): Promise => { const currentTitle = savedVis.title; savedVis.title = newTitle; embeddableHandler.updateInput({ title: newTitle }); @@ -630,7 +634,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 @@ -660,7 +664,7 @@ export const getTopNavConfig = ( ); } - let saveModal; + let saveModal: React.ReactElement; if (originatingApp) { saveModal = ( @@ -691,7 +695,7 @@ export const getTopNavConfig = ( ); } else { saveModal = ( - void; + onSave: (props: OnSaveGraphProps) => Promise; onClose: () => void; title: string; description: string; @@ -37,9 +41,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 1ded967b744b6..c895b167da71b 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.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 f0ce852cbb28a..001bf0b408123 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 f287e4426eaaa..ec8e45feb342c 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 @@ -172,8 +172,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..d2386c29bf2d8 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 @@ -9,12 +9,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Adapters } from '@kbn/inspector-plugin/public'; import { - SavedObjectSaveModalOrigin, OnSaveProps, - showSaveModal, + SaveResult, + ShowSaveModalMinimalSaveModalProps, } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectSaveModalOrigin, 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 +30,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 +172,7 @@ export function getTopNavConfig({ dashboardId?: string | null; addToLibrary: boolean; } - ) => { + ): Promise => { try { await checkForDuplicateTitle( { @@ -210,7 +213,7 @@ export function getTopNavConfig({ }), }; - let saveModal; + let saveModal: React.ReactElement; if (savedMap.hasOriginatingApp()) { saveModal = ( @@ -234,7 +237,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 3b6e7180bcfaf..d720344917504 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 @@ -359,7 +359,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 159b236af646e..d0c032c5ce534 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 = {