From c024f9c6fa050621f09197e1265ebe16f2d85441 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Jul 2023 13:41:21 -0400 Subject: [PATCH] Change links mapping from object to array of links This makes it easier to use the Elasticsearch `_update` API for saved objects. With arrays we are always replacing the entire selection. Using `_update` with an object mapping applies only partial updates. So it was not possible to delete a link. --- .../current_mappings.json | 1 - .../public/lib/actions/edit_panel_action.ts | 7 +++ .../common/content_management/index.ts | 1 - .../content_management/v1/cm_services.ts | 2 +- .../common/content_management/v1/index.ts | 1 - .../common/content_management/v1/types.ts | 6 +- .../navigation_embeddable/common/types.ts | 2 +- .../navigation_embeddable_component.tsx | 5 +- .../navigation_embeddable_panel_editor.tsx | 11 ++-- .../navigation_embeddable_editor_tools.tsx | 21 +++---- .../public/editor/open_editor_flyout.tsx | 4 +- .../embeddable/navigation_embeddable.tsx | 61 +++++++++++-------- .../navigation_embeddable_factory.ts | 8 +-- .../saved_objects/navigation_embeddable.ts | 1 - 14 files changed, 66 insertions(+), 65 deletions(-) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 101c986a30a56..169221d659e53 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1055,7 +1055,6 @@ } }, "navigation_embeddable": { - "dynamic": false, "properties": { "id": { "type": "text" diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 98e541fd08e6f..037295f5617a2 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -19,6 +19,7 @@ import { EmbeddableStateTransfer, EmbeddableInput, Container, + isReferenceOrValueEmbeddable, } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -94,6 +95,12 @@ export class EditPanelAction implements Action { const oldExplicitInput = embeddable.getExplicitInput(); const newExplicitInput = await factory.getExplicitInput(oldExplicitInput, embeddable.parent); embeddable.parent?.replaceEmbeddable(embeddable.id, newExplicitInput); + if ( + isReferenceOrValueEmbeddable(embeddable) && + embeddable.inputIsRefType(embeddable.getInput()) + ) { + embeddable.reload(); + } return; } diff --git a/src/plugins/navigation_embeddable/common/content_management/index.ts b/src/plugins/navigation_embeddable/common/content_management/index.ts index 7228ad084a20a..282ba879c17fc 100644 --- a/src/plugins/navigation_embeddable/common/content_management/index.ts +++ b/src/plugins/navigation_embeddable/common/content_management/index.ts @@ -16,7 +16,6 @@ export type { NavigationEmbeddableItem, NavigationLinkType, NavigationEmbeddableLink, - NavigationEmbeddableLinkList, } from './latest'; export { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from './latest'; diff --git a/src/plugins/navigation_embeddable/common/content_management/v1/cm_services.ts b/src/plugins/navigation_embeddable/common/content_management/v1/cm_services.ts index a195dff92e069..5494a193ba7b5 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/cm_services.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/cm_services.ts @@ -29,7 +29,7 @@ const navigationEmbeddableAttributesSchema = schema.object( { title: schema.string(), description: schema.maybe(schema.string()), - links: schema.maybe(schema.recordOf(schema.string(), navigationEmbeddableLinkSchema)), + links: schema.maybe(schema.arrayOf(navigationEmbeddableLinkSchema)), }, { unknowns: 'forbid' } ); diff --git a/src/plugins/navigation_embeddable/common/content_management/v1/index.ts b/src/plugins/navigation_embeddable/common/content_management/v1/index.ts index 3f8f2a727d345..bedc5a6ff2f08 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/index.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/index.ts @@ -11,7 +11,6 @@ export type { NavigationEmbeddableCrudTypes, NavigationEmbeddableAttributes, NavigationEmbeddableLink, - NavigationEmbeddableLinkList, NavigationLinkType, } from './types'; export type NavigationEmbeddableItem = NavigationEmbeddableCrudTypes['Item']; diff --git a/src/plugins/navigation_embeddable/common/content_management/v1/types.ts b/src/plugins/navigation_embeddable/common/content_management/v1/types.ts index d88d84f2179be..0d1a87a17d148 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/types.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/types.ts @@ -38,13 +38,9 @@ export interface NavigationEmbeddableLink { order: number; } -export interface NavigationEmbeddableLinkList { - [id: string]: NavigationEmbeddableLink; -} - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type NavigationEmbeddableAttributes = { title: string; description?: string; - links?: NavigationEmbeddableLinkList; + links?: NavigationEmbeddableLink[]; }; diff --git a/src/plugins/navigation_embeddable/common/types.ts b/src/plugins/navigation_embeddable/common/types.ts index eb59618099add..e03b4a4dd1469 100644 --- a/src/plugins/navigation_embeddable/common/types.ts +++ b/src/plugins/navigation_embeddable/common/types.ts @@ -8,7 +8,7 @@ import type { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; -export type NavigationEmbeddableContentType = 'navigationEmbeddable'; +export type NavigationEmbeddableContentType = 'navigation_embeddable'; // TODO does this type need to be versioned? export interface SharingSavedObjectProps { 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 f34134ee113ae..a45d6d8028676 100644 --- a/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx +++ b/src/plugins/navigation_embeddable/public/components/navigation_embeddable_component.tsx @@ -15,11 +15,14 @@ import { useNavigationEmbeddable } from '../embeddable/navigation_embeddable'; import { ExternalLinkComponent } from './external_link/external_link_component'; import { DashboardLinkComponent } from './dashboard_link/dashboard_link_component'; import { memoizedGetOrderedLinkList } from '../editor/navigation_embeddable_editor_tools'; +import { NavigationEmbeddableByValueInput } from '../embeddable/types'; export const NavigationEmbeddableComponent = () => { const navEmbeddable = useNavigationEmbeddable(); - const links = navEmbeddable.select((state) => state.output.attributes?.links); + const links = navEmbeddable.select( + (state) => (state.explicitInput as NavigationEmbeddableByValueInput).attributes?.links + ); const orderedLinks = useMemo(() => { if (!links) return []; 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 57954090170e1..9cef29153300b 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 @@ -39,7 +39,6 @@ import { coreServices } from '../services/kibana_services'; import { NavigationEmbeddableAttributes, NavigationEmbeddableLink, - NavigationEmbeddableLinkList, } from '../../common/content_management'; import { NavEmbeddableStrings } from './navigation_embeddable_strings'; @@ -84,7 +83,11 @@ const NavigationEmbeddablePanelEditor = ({ const onDragEnd = useCallback( ({ source, destination }) => { if (source && destination) { - const newList = euiDragDropReorder(orderedLinks, source.index, destination.index); + const newList = euiDragDropReorder(orderedLinks, source.index, destination.index).map( + (link, i) => { + return { ...link, order: i }; + } + ); setOrderedLinks(newList); } }, @@ -136,9 +139,7 @@ const NavigationEmbeddablePanelEditor = ({ isLoading={isSaving} onClick={async () => { setIsSaving(true); - const newLinks = orderedLinks.reduce((prev, link, i) => { - return { ...prev, [link.id]: { ...link, order: i } }; - }, {} as NavigationEmbeddableLinkList); + const newLinks = [...orderedLinks]; const newAttributes: NavigationEmbeddableAttributes = { title: libraryTitle, links: newLinks, diff --git a/src/plugins/navigation_embeddable/public/editor/navigation_embeddable_editor_tools.tsx b/src/plugins/navigation_embeddable/public/editor/navigation_embeddable_editor_tools.tsx index 8875ec4a76d86..4248af756f525 100644 --- a/src/plugins/navigation_embeddable/public/editor/navigation_embeddable_editor_tools.tsx +++ b/src/plugins/navigation_embeddable/public/editor/navigation_embeddable_editor_tools.tsx @@ -7,19 +7,12 @@ */ import { memoize } from 'lodash'; -import { - NavigationEmbeddableLink, - NavigationEmbeddableLinkList, -} from '../../common/content_management'; +import { NavigationEmbeddableLink } from '../../common/content_management'; -const getOrderedLinkList = (links: NavigationEmbeddableLinkList): NavigationEmbeddableLink[] => { - return Object.keys(links) - .map((linkId) => { - return links[linkId]; - }) - .sort((linkA, linkB) => { - return linkA.order - linkB.order; - }); +const getOrderedLinkList = (links: NavigationEmbeddableLink[]): NavigationEmbeddableLink[] => { + return [...links].sort((linkA, linkB) => { + return linkA.order - linkB.order; + }); }; /** @@ -28,10 +21,10 @@ const getOrderedLinkList = (links: NavigationEmbeddableLinkList): NavigationEmbe * calculated this so, we can get away with using the cached version in the editor */ export const memoizedGetOrderedLinkList = memoize( - (links: NavigationEmbeddableLinkList) => { + (links: NavigationEmbeddableLink[]) => { return getOrderedLinkList(links); }, - (links: NavigationEmbeddableLinkList) => { + (links: NavigationEmbeddableLink[]) => { return links; } ); 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 9a944bd8e7847..51a2542d503ca 100644 --- a/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx +++ b/src/plugins/navigation_embeddable/public/editor/open_editor_flyout.tsx @@ -53,9 +53,11 @@ export async function openEditorFlyout( const onSave = async (newAttributes: NavigationEmbeddableAttributes, useRefType: boolean) => { const newInput = await getNavigationEmbeddableAttributeService().wrapAttributes( newAttributes, - useRefType + useRefType, + initialInput ); resolve(newInput); + parentDashboard?.reload(); editorFlyout.close(); }; diff --git a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx index 1c994d7dc00ea..6ad30a98259b9 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable.tsx @@ -7,6 +7,7 @@ */ import React, { createContext, useContext } from 'react'; +import { Subscription } from 'rxjs'; import { AttributeService, @@ -16,7 +17,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, @@ -57,8 +58,7 @@ export class NavigationEmbeddable public readonly type = NAVIGATION_EMBEDDABLE_TYPE; deferEmbeddableLoad = true; - private attributes?: NavigationEmbeddableAttributes; - private savedObjectId?: string; + private isDestroyed?: boolean; private subscriptions: Subscription = new Subscription(); // state management @@ -101,23 +101,31 @@ export class NavigationEmbeddable 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(); + this.initializeSavedLinks(initialInput) + .then(() => this.setInitializationFinished()) + .catch((e: Error) => this.onFatalError(e)); + } + + private async initializeSavedLinks(input: NavigationEmbeddableInput) { + const { metaInfo, attributes } = await this.attributeService.unwrapAttributes(input); + if (this.isDestroyed) return; + + // TODO handle metaInfo + + this.updateInput({ attributes }); + + await this.initializeOutput(); + } + + private async initializeOutput() { + const { attributes } = this.getInput() as NavigationEmbeddableByValueInput; + const { title, description } = this.getInput(); + this.updateOutput({ + defaultTitle: attributes.title, + defaultDescription: attributes.description, + title: title ?? attributes.title, + description: description ?? attributes.description, + }); } public inputIsRefType( @@ -137,21 +145,20 @@ export class NavigationEmbeddable } public async reload() { - this.attributes = (await this.attributeService.unwrapAttributes(this.input)).attributes; - - this.updateOutput({ - attributes: this.attributes, - defaultTitle: this.attributes.title, - defaultDescription: this.attributes.description, - }); + if (this.isDestroyed) return; + await this.initializeSavedLinks(this.getInput()); + this.render(); } public destroy() { + this.isDestroyed = true; super.destroy(); + this.subscriptions.unsubscribe(); this.cleanupStateTools(); } public render() { + if (this.isDestroyed) return; return ( 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 0ab7ddaace452..0600148b0880f 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts @@ -82,14 +82,10 @@ export class NavigationEmbeddableFactoryDefinition if (!(input as NavigationEmbeddableByReferenceInput).savedObjectId) { (input as NavigationEmbeddableByReferenceInput).savedObjectId = savedObjectId; } - return this.create(input, parent, savedObjectId); + return this.create(input, parent); } - public async create( - initialInput: NavigationEmbeddableInput, - parent: DashboardContainer, - savedObjectId?: string - ) { + public async create(initialInput: NavigationEmbeddableInput, parent: DashboardContainer) { await untilPluginStartServicesReady(); const reduxEmbeddablePackage = await lazyLoadReduxToolsPackage(); diff --git a/src/plugins/navigation_embeddable/server/saved_objects/navigation_embeddable.ts b/src/plugins/navigation_embeddable/server/saved_objects/navigation_embeddable.ts index 1282f61c4cfcc..e6d13c0fad67f 100644 --- a/src/plugins/navigation_embeddable/server/saved_objects/navigation_embeddable.ts +++ b/src/plugins/navigation_embeddable/server/saved_objects/navigation_embeddable.ts @@ -25,7 +25,6 @@ export const navigationEmbeddableSavedObjectType: SavedObjectsType = { }, }, mappings: { - dynamic: false, properties: { id: { type: 'text' }, title: { type: 'text' },