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 ffdb0b042e173..4d58008365ec2 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 @@ -10,7 +10,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { OnSaveProps, SaveResult } from '@kbn/saved-objects-plugin/public'; -import { showSaveModal, SavedObjectSaveModal } from '@kbn/saved-objects-plugin/public'; +import { + showSaveModal, + SavedObjectSaveModalWithSaveResult, +} from '@kbn/saved-objects-plugin/public'; import { LINKS_EMBEDDABLE_TYPE, CONTENT_ID } from '../../common'; import { checkForDuplicateTitle } from './duplicate_title_check'; import { linksClient } from './links_content_management_client'; @@ -71,7 +74,7 @@ export const runSaveToLibrary = async (newState: EditorState): Promise 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 afef35c9268e0..87ad3813454c9 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 @@ -16,7 +16,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, @@ -27,7 +32,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 d39d76e742a04..480234c4271a1 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,10 @@ import { getTitle, } from '@kbn/presentation-publishing'; import type { OnSaveProps, SaveResult } from '@kbn/saved-objects-plugin/public'; -import { SavedObjectSaveModal, showSaveModal } from '@kbn/saved-objects-plugin/public'; +import { + SavedObjectSaveModalWithSaveResult, + showSaveModal, +} from '@kbn/saved-objects-plugin/public'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; @@ -112,7 +115,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 ( - = const [timeRestore, setTimeRestore] = useState(Boolean(isTimeBased && savedTimeRestore)); const [currentTags, setCurrentTags] = useState(tags); - const onModalSave = (params: OnSaveProps) => { - onSave({ + const onModalSave = async (params: OnSaveProps) => { + await onSave({ ...params, newTimeRestore: timeRestore, newTags: currentTags, diff --git a/src/platform/plugins/shared/presentation_util/public/components/index.tsx b/src/platform/plugins/shared/presentation_util/public/components/index.tsx index e7a8807a793c0..7899a02295bbe 100644 --- a/src/platform/plugins/shared/presentation_util/public/components/index.tsx +++ b/src/platform/plugins/shared/presentation_util/public/components/index.tsx @@ -40,6 +40,13 @@ export const LazySavedObjectSaveModalDashboard = React.lazy( () => 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 b90842a68dc92..c5f8d8767fb30 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 @@ -18,7 +18,7 @@ import type { 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); @@ -72,7 +72,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 @@ -85,7 +85,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 d410f630f9485..fede6c4e35258 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 c90a04367d53a..aeadd1f231e0b 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 @@ -35,6 +35,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; @@ -57,8 +58,8 @@ export interface SaveDashboardReturn { redirectRequired?: boolean; } -interface Props { - onSave: (props: OnSaveProps) => void; +interface Props { + onSave: (props: OnSaveProps) => Promise; onClose: () => void; title: string; showCopyOnSave: boolean; @@ -81,7 +82,7 @@ export interface SaveModalState { copyOnSave: boolean; isTitleDuplicateConfirmed: boolean; hasTitleDuplicate: boolean; - isLoading: boolean; + isSaving: boolean; visualizationDescription: string; hasAttemptedSubmit: boolean; } @@ -92,8 +93,8 @@ const generateId = htmlIdGenerator(); * @deprecated * @removeBy 8.8.0 */ -class SavedObjectSaveModalComponent extends React.Component< - Props, +class SavedObjectSaveModalComponent extends React.Component< + Props, SaveModalState, WithEuiThemeProps > { @@ -105,7 +106,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, }; @@ -257,7 +258,7 @@ class SavedObjectSaveModalComponent extends React.Component< private onTitleDuplicate = () => { this.setState({ - isLoading: false, + isSaving: false, isTitleDuplicateConfirmed: true, hasTitleDuplicate: true, }); @@ -268,18 +269,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, @@ -289,7 +284,7 @@ class SavedObjectSaveModalComponent extends React.Component< newDescription: this.state.visualizationDescription, }); } finally { - this.setState({ isLoading: false }); + this.setState({ isSaving: false }); } }; @@ -349,7 +344,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', @@ -366,7 +361,7 @@ class SavedObjectSaveModalComponent extends React.Component< @@ -443,4 +438,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 832de5e0ee721..f079d077ecf8b 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,8 +12,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { OnSaveProps, SaveModalState } from '.'; -import { SavedObjectSaveModal } from '.'; +import type { OnSaveProps, SaveModalState, SaveResult } from '.'; +import { SavedObjectSaveModalWithSaveResult } from '.'; interface SaveModalDocumentInfo { id?: string; @@ -30,7 +30,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) { @@ -89,8 +89,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 @@ -100,7 +100,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 @@ -51,7 +57,7 @@ export function showSaveModal( const onSave = saveModal.props.onSave; - const onSaveConfirmed: MinimalSaveModalProps['onSave'] = async (...args) => { + const onSaveConfirmed: ShowSaveModalMinimalSaveModalProps['onSave'] = async (...args) => { try { const response = await onSave(...args); // close modal if we either hit an error or the saved object got an id 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 c65dca96fd3d7..dcea74e269bbe 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,7 +11,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { get, omit } from 'lodash'; import type { OnSaveProps, SaveResult } from '@kbn/saved-objects-plugin/public'; -import { SavedObjectSaveModal, showSaveModal } from '@kbn/saved-objects-plugin/public'; +import { + SavedObjectSaveModalWithSaveResult, + showSaveModal, +} from '@kbn/saved-objects-plugin/public'; import { getNotifications } from '../../services'; import type { VisualizeByReferenceInput, @@ -148,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 a475dec720de7..04ec82ca86cc9 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 @@ -17,10 +17,15 @@ import { parse } from 'query-string'; import type { Capabilities } from '@kbn/core/public'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; -import type { SavedObjectSaveOpts, OnSaveProps } from '@kbn/saved-objects-plugin/public'; +import type { + SavedObjectSaveOpts, + OnSaveProps, + ShowSaveModalMinimalSaveModalProps, + SaveResult, +} from '@kbn/saved-objects-plugin/public'; import { showSaveModal, SavedObjectSaveModalOrigin } from '@kbn/saved-objects-plugin/public'; import { - LazySavedObjectSaveModalDashboard, + LazySavedObjectSaveModalDashboardWithSaveResult, withSuspense, } from '@kbn/presentation-util-plugin/public'; import { unhashUrl } from '@kbn/kibana-utils-plugin/public'; @@ -72,7 +77,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; @@ -580,7 +587,7 @@ export const getTopNavConfig = ( }: OnSaveProps & { returnToOrigin?: boolean } & { dashboardId?: string | null; addToLibrary?: boolean; - }) => { + }): Promise => { const currentTitle = savedVis.title; savedVis.title = newTitle; embeddableHandler.updateInput({ title: newTitle }); @@ -616,7 +623,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 @@ -653,7 +660,7 @@ export const getTopNavConfig = ( ); } - let saveModal; + let saveModal: React.ReactElement; if (originatingApp) { saveModal = ( @@ -684,7 +691,7 @@ export const getTopNavConfig = ( ); } else { saveModal = ( - void; + onSave: (props: OnSaveGraphProps) => Promise; onClose: () => void; title: string; description: string; @@ -38,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 d7fde739214df..f3df101226fae 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 @@ -44,14 +44,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; @@ -69,6 +69,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 401123777babe..90c471ad38421 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 @@ -33,7 +33,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; } @@ -85,9 +85,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 638e0369ee841..1b3bcd7eadcd0 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 b0aa8a013d149..a192625a8ffdc 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 @@ -28,7 +28,7 @@ export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit< > & { initialTags: string[]; savedObjectsTagging?: SavedObjectTaggingPluginStart; - onSave: (props: DashboardSaveProps) => void; + onSave: (props: DashboardSaveProps) => Promise; getOriginatingPath?: (dashboardId: string) => string; }; @@ -53,9 +53,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 1e79f137ed4d3..3981fa243fb57 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 @@ -20,7 +20,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< @@ -57,11 +57,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 a4dc2963cdb9d..2e655383f462d 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 b84e5238f65b3..ece0f0209669b 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 @@ -50,7 +50,7 @@ export const SaveModal = ({ }: { domElement: HTMLDivElement; savedObjectsTagging: SavedObjectTaggingPluginStart | undefined; - onSave: (props: ModalOnSaveProps) => void; + onSave: (props: ModalOnSaveProps) => Promise; title: string; description: string; tags: string[]; @@ -62,7 +62,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 6d4a070dba699..c54de4d74203e 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 @@ -8,10 +8,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { Adapters } from '@kbn/inspector-plugin/public'; -import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; +import type { + OnSaveProps, + 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 type { ScopedHistory } from '@kbn/core/public'; @@ -26,7 +30,9 @@ import { MAP_EMBEDDABLE_NAME } from '../../../common/constants'; import type { SavedMap } from './saved_map'; import { checkForDuplicateTitle } from '../../content_management'; -const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); +const SavedObjectSaveModalDashboardWithSaveResult = withSuspense( + LazySavedObjectSaveModalDashboardWithSaveResult +); export function getTopNavConfig({ savedMap, @@ -166,7 +172,7 @@ export function getTopNavConfig({ dashboardId?: string | null; addToLibrary: boolean; } - ) => { + ): Promise => { try { await checkForDuplicateTitle( { @@ -207,7 +213,7 @@ export function getTopNavConfig({ }), }; - let saveModal; + let saveModal: React.ReactElement; if (savedMap.hasOriginatingApp()) { saveModal = ( @@ -231,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 7f56651023e2b..53838d3b6073a 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 @@ -393,7 +393,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 f5661fefc9d4c..0cc0542e00bd6 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 17115f79a964d..d28c2362b8343 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 240dc0e5d5305..76f405a4bf85d 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 @@ -57,7 +57,7 @@ export const useAddToDashboard = ({ const { embeddable } = useKibana().services; const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback( - ({ dashboardId }) => { + async ({ dashboardId }) => { const stateTransfer = embeddable.getStateTransfer(); const state = {