diff --git a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx index 9aa0845b653df..f34134ee113ae 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx @@ -19,9 +19,10 @@ import { memoizedGetOrderedLinkList } from '../editor/navigation_embeddable_edit export const NavigationEmbeddableComponent = () => { const navEmbeddable = useNavigationEmbeddable(); - const links = navEmbeddable.select((state) => state.output.links) ?? {}; + const links = navEmbeddable.select((state) => state.output.attributes?.links); const orderedLinks = useMemo(() => { + if (!links) return []; return memoizedGetOrderedLinkList(links); }, [links]); diff --git a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor.tsx b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor.tsx index 82b359dafd69d..57954090170e1 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor.tsx +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor.tsx @@ -49,7 +49,6 @@ import { NavigationEmbeddablePanelEditorLink } from './navigation_embeddable_pan import noLinksIllustrationDark from '../assets/empty_links_dark.svg'; import noLinksIllustrationLight from '../assets/empty_links_light.svg'; - import './navigation_embeddable.scss'; const NavigationEmbeddablePanelEditor = ({ @@ -59,18 +58,19 @@ const NavigationEmbeddablePanelEditor = ({ savedObjectId, parentDashboard, }: { - onSave: (attributes: NavigationEmbeddableAttributes, useRefType: boolean) => void; + onSave: (newAttributes: NavigationEmbeddableAttributes, useRefType: boolean) => void; onClose: () => void; - parentDashboard?: DashboardContainer; attributes?: NavigationEmbeddableAttributes; savedObjectId?: string; + parentDashboard?: DashboardContainer; }) => { const isDarkTheme = useObservable(coreServices.theme.theme$)?.darkMode; const editLinkFlyoutRef: React.RefObject = useMemo(() => React.createRef(), []); const [orderedLinks, setOrderedLinks] = useState([]); - const [saveToLibrary, setSaveToLibrary] = useState(false); - const [libraryTitle, setLibraryTitle] = useState(); + const [saveToLibrary, setSaveToLibrary] = useState(Boolean(savedObjectId)); + const [libraryTitle, setLibraryTitle] = useState(attributes?.title ?? ''); + const [isSaving, setIsSaving] = useState(false); useEffect(() => { const initialLinks = attributes?.links; @@ -133,15 +133,23 @@ const NavigationEmbeddablePanelEditor = ({ const button = ( { + isLoading={isSaving} + onClick={async () => { + setIsSaving(true); const newLinks = orderedLinks.reduce((prev, link, i) => { return { ...prev, [link.id]: { ...link, order: i } }; }, {} as NavigationEmbeddableLinkList); - const newAttributes = { ...attributes, title: libraryTitle, links: newLinks }; - onSave(newAttributes, !Boolean(savedObjectId)); + const newAttributes: NavigationEmbeddableAttributes = { + title: libraryTitle, + links: newLinks, + }; + await onSave(newAttributes, Boolean(savedObjectId) || saveToLibrary); + setIsSaving(false); }} > - {NavEmbeddableStrings.editor.panelEditor.getSaveButtonLabel()} + {savedObjectId + ? NavEmbeddableStrings.editor.panelEditor.getUpdateLibraryItemButtonLabel() + : NavEmbeddableStrings.editor.panelEditor.getSaveButtonLabel()} ); @@ -152,7 +160,7 @@ const NavigationEmbeddablePanelEditor = ({ {button} ); - }, [onSave, orderedLinks, attributes, saveToLibrary, libraryTitle, savedObjectId]); + }, [onSave, isSaving, orderedLinks, saveToLibrary, libraryTitle, savedObjectId]); return ( <> diff --git a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor_link.tsx b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor_link.tsx index be65c130222eb..7c6d2b7268102 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor_link.tsx +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_panel_editor_link.tsx @@ -21,11 +21,8 @@ import { } from '@elastic/eui'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; -import { - NavigationLinkInfo, - DASHBOARD_LINK_TYPE, - NavigationEmbeddableLink, -} from '../embeddable/types'; +import { NavigationLinkInfo } from '../embeddable/types'; +import { DASHBOARD_LINK_TYPE, NavigationEmbeddableLink } from '../../common/content_management'; import { fetchDashboard } from './dashboard_link/dashboard_link_tools'; import { NavEmbeddableStrings } from './navigation_embeddable_strings'; diff --git a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts index 109b868df41a8..93b2f7f005a68 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_strings.ts @@ -51,6 +51,14 @@ export const NavEmbeddableStrings = { i18n.translate('navigationEmbeddable.panelEditor.saveButtonLabel', { defaultMessage: 'Save', }), + getSaveToLibraryButtonLabel: () => + i18n.translate('navigationEmbeddable.panelEditor.saveToLibraryButtonLabel', { + defaultMessage: 'Save to library', + }), + getUpdateLibraryItemButtonLabel: () => + i18n.translate('navigationEmbeddable.panelEditor.updateLibraryItemButtonLabel', { + defaultMessage: 'Update library item', + }), getTitleInputLabel: () => i18n.translate('navigationEmbeddable.panelEditor.titleInputLabel', { defaultMessage: 'Title', diff --git a/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx b/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx index 63c346003b74c..9a944bd8e7847 100644 --- a/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx +++ b/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx @@ -16,11 +16,14 @@ import { EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; +import { NavigationEmbeddableAttributes } from '../../common/content_management'; import { coreServices } from '../services/kibana_services'; -import { NavigationEmbeddableInput } from '../embeddable/types'; +import { + NavigationEmbeddableByReferenceInput, + NavigationEmbeddableInput, +} from '../embeddable/types'; import { memoizedFetchDashboards } from '../components/dashboard_link/dashboard_link_tools'; import { getNavigationEmbeddableAttributeService } from '../services/attribute_service'; -import { NavigationEmbeddableAttributes } from '../../common/content_management'; const LazyNavigationEmbeddablePanelEditor = React.lazy( () => import('../components/navigation_embeddable_panel_editor') @@ -43,15 +46,16 @@ export async function openEditorFlyout( const { attributes } = await getNavigationEmbeddableAttributeService().unwrapAttributes( initialInput ); + return new Promise((resolve, reject) => { const closed$ = new Subject(); const onSave = async (newAttributes: NavigationEmbeddableAttributes, useRefType: boolean) => { - const wrappedAttributes = (await getNavigationEmbeddableAttributeService()).wrapAttributes( + const newInput = await getNavigationEmbeddableAttributeService().wrapAttributes( newAttributes, useRefType ); - resolve(wrappedAttributes); + resolve(newInput); editorFlyout.close(); }; @@ -76,6 +80,7 @@ export async function openEditorFlyout( onClose={onCancel} onSave={onSave} parentDashboard={parentDashboard} + savedObjectId={(initialInput as NavigationEmbeddableByReferenceInput).savedObjectId} />, { theme$: coreServices.theme.theme$ } ), diff --git a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx index 702f9faccb5e8..1c994d7dc00ea 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx @@ -16,6 +16,7 @@ import { } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; +import { Subscription } from 'rxjs'; import { navigationEmbeddableReducers } from './navigation_embeddable_reducers'; import { NavigationEmbeddableByReferenceInput, @@ -56,6 +57,10 @@ export class NavigationEmbeddable public readonly type = NAVIGATION_EMBEDDABLE_TYPE; deferEmbeddableLoad = true; + private attributes?: NavigationEmbeddableAttributes; + private savedObjectId?: string; + private subscriptions: Subscription = new Subscription(); + // state management public select: NavigationReduxEmbeddableTools['select']; public getState: NavigationReduxEmbeddableTools['getState']; @@ -63,7 +68,6 @@ export class NavigationEmbeddable public onStateChange: NavigationReduxEmbeddableTools['onStateChange']; private cleanupStateTools: () => void; - private attributes?: NavigationEmbeddableAttributes; constructor( reduxToolsPackage: ReduxToolsPackage, @@ -96,6 +100,23 @@ export class NavigationEmbeddable this.dispatch = reduxEmbeddableTools.dispatch; this.cleanupStateTools = reduxEmbeddableTools.cleanup; this.onStateChange = reduxEmbeddableTools.onStateChange; + + this.subscriptions.add( + this.getInput$().subscribe(async () => { + const savedObjectId = (this.getInput() as NavigationEmbeddableByReferenceInput) + .savedObjectId; + const attributes = (this.getInput() as NavigationEmbeddableByValueInput).attributes; + if (this.attributes !== attributes || this.savedObjectId !== savedObjectId) { + this.savedObjectId = savedObjectId; + this.reload(); + } else { + this.updateOutput({ + attributes: this.attributes, + defaultTitle: this.attributes.title, + }); + } + }) + ); this.setInitializationFinished(); } @@ -116,10 +137,12 @@ export class NavigationEmbeddable } public async reload() { + this.attributes = (await this.attributeService.unwrapAttributes(this.input)).attributes; + this.updateOutput({ - defaultTitle: this.attributes?.title, - defaultDescription: this.attributes?.description, - links: this.attributes?.links, + attributes: this.attributes, + defaultTitle: this.attributes.title, + defaultDescription: this.attributes.description, }); } diff --git a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts index 0e4ea02d2d183..0ab7ddaace452 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts @@ -17,7 +17,11 @@ import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public' import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; -import { NavigationEmbeddableByReferenceInput, NavigationEmbeddableInput } from './types'; +import { + NavigationEmbeddableByReferenceInput, + NavigationEmbeddableByValueInput, + NavigationEmbeddableInput, +} from './types'; import type { NavigationEmbeddable } from './navigation_embeddable'; import { coreServices, untilPluginStartServicesReady } from '../services/kibana_services'; import { getNavigationEmbeddableAttributeService } from '../services/attribute_service'; @@ -78,10 +82,14 @@ export class NavigationEmbeddableFactoryDefinition if (!(input as NavigationEmbeddableByReferenceInput).savedObjectId) { (input as NavigationEmbeddableByReferenceInput).savedObjectId = savedObjectId; } - return this.create(input, parent); + return this.create(input, parent, savedObjectId); } - public async create(initialInput: NavigationEmbeddableInput, parent: DashboardContainer) { + public async create( + initialInput: NavigationEmbeddableInput, + parent: DashboardContainer, + savedObjectId?: string + ) { await untilPluginStartServicesReady(); const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage(); @@ -106,7 +114,10 @@ export class NavigationEmbeddableFactoryDefinition const { openEditorFlyout } = await import('../editor/open_editor_flyout'); const input = await openEditorFlyout( - { ...getDefaultNavigationEmbeddableInput(), ...initialInput }, + { + ...getDefaultNavigationEmbeddableInput(), + ...initialInput, + } as NavigationEmbeddableByValueInput, parent ).catch(() => { // swallow the promise rejection that happens when the flyout is closed diff --git a/src/plugins/navigation_embeddable/public/embeddable/types.ts b/src/plugins/navigation_embeddable/public/embeddable/types.ts index 11385b504a851..f9a086f06d27e 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/types.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/types.ts @@ -20,7 +20,6 @@ import { EXTERNAL_LINK_TYPE, NavigationLinkType, NavigationEmbeddableAttributes, - NavigationEmbeddableLinkList, } from '../../common/content_management'; export const NavigationLinkInfo: { @@ -49,7 +48,7 @@ export type NavigationEmbeddableInput = | NavigationEmbeddableByReferenceInput; export type NavigationEmbeddableOutput = EmbeddableOutput & { - links?: NavigationEmbeddableLinkList; + attributes?: NavigationEmbeddableAttributes; }; /** diff --git a/src/plugins/navigation_embeddable/public/services/attribute_service.ts b/src/plugins/navigation_embeddable/public/services/attribute_service.ts index d0b2b9f4f74a4..36bd21fe50a88 100644 --- a/src/plugins/navigation_embeddable/public/services/attribute_service.ts +++ b/src/plugins/navigation_embeddable/public/services/attribute_service.ts @@ -19,7 +19,7 @@ import { embeddableService } from './kibana_services'; import { navigationEmbeddableClient } from '../content_management'; import { NAVIGATION_EMBEDDABLE_TYPE } from '../../common/constants'; -type NavigationEmbeddableDocument = NavigationEmbeddableAttributes & { +export type NavigationEmbeddableDocument = NavigationEmbeddableAttributes & { references?: Reference[]; };