diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts index 0989c46c6d975..efeaa76297f9e 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/find_dashboards.ts @@ -80,6 +80,7 @@ export async function findDashboardById( id, status: 'success', attributes: cachedDashboard.item.attributes, + references: cachedDashboard.item.references, }; } /** Otherwise, fetch the dashboard from the content management client, add it to the cache, and return the result */ diff --git a/src/plugins/navigation_embeddable/common/content_management/index.ts b/src/plugins/navigation_embeddable/common/content_management/index.ts index 7b26870c7ce53..1dbb901c8cf8f 100644 --- a/src/plugins/navigation_embeddable/common/content_management/index.ts +++ b/src/plugins/navigation_embeddable/common/content_management/index.ts @@ -24,6 +24,7 @@ export { DASHBOARD_LINK_TYPE, NAV_VERTICAL_LAYOUT, NAV_HORIZONTAL_LAYOUT, + EXTERNAL_LINK_SUPPORTED_PROTOCOLS, } from './latest'; export * as NavigationEmbeddableV1 from './v1'; 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 3c9c7a1bb759c..9e4452cb09c0e 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 @@ -16,21 +16,35 @@ import { objectTypeToGetResultSchema, } from '@kbn/content-management-utils'; import { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from '.'; -import { NAV_HORIZONTAL_LAYOUT, NAV_VERTICAL_LAYOUT } from './constants'; +import { + EXTERNAL_LINK_SUPPORTED_PROTOCOLS, + NAV_HORIZONTAL_LAYOUT, + NAV_VERTICAL_LAYOUT, +} from './constants'; -const navigationEmbeddableLinkSchema = schema.object({ +const baseNavigationEmbeddableLinkSchema = { id: schema.string(), - type: schema.oneOf([schema.literal(DASHBOARD_LINK_TYPE), schema.literal(EXTERNAL_LINK_TYPE)]), - destination: schema.string(), label: schema.maybe(schema.string()), order: schema.number(), +}; + +const dashboardLinkSchema = schema.object({ + ...baseNavigationEmbeddableLinkSchema, + destinationRefName: schema.string(), + type: schema.literal(DASHBOARD_LINK_TYPE), +}); + +const externalLinkSchema = schema.object({ + ...baseNavigationEmbeddableLinkSchema, + type: schema.literal(EXTERNAL_LINK_TYPE), + destination: schema.uri({ scheme: EXTERNAL_LINK_SUPPORTED_PROTOCOLS }), }); const navigationEmbeddableAttributesSchema = schema.object( { title: schema.string(), description: schema.maybe(schema.string()), - links: schema.maybe(schema.arrayOf(navigationEmbeddableLinkSchema)), + links: schema.arrayOf(schema.oneOf([dashboardLinkSchema, externalLinkSchema])), layout: schema.maybe( schema.oneOf([schema.literal(NAV_HORIZONTAL_LAYOUT), schema.literal(NAV_VERTICAL_LAYOUT)]) ), diff --git a/src/plugins/navigation_embeddable/common/content_management/v1/constants.ts b/src/plugins/navigation_embeddable/common/content_management/v1/constants.ts index 70f1af5c0f69d..a03894ba6b715 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/constants.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/constants.ts @@ -17,3 +17,5 @@ export const EXTERNAL_LINK_TYPE = 'externalLink'; */ export const NAV_HORIZONTAL_LAYOUT = 'horizontal'; export const NAV_VERTICAL_LAYOUT = 'vertical'; + +export const EXTERNAL_LINK_SUPPORTED_PROTOCOLS = ['http', 'https', 'mailto']; 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 efda7e1cf696c..b0af2ee1a936a 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/index.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/index.ts @@ -20,4 +20,5 @@ export { DASHBOARD_LINK_TYPE, NAV_VERTICAL_LAYOUT, NAV_HORIZONTAL_LAYOUT, + EXTERNAL_LINK_SUPPORTED_PROTOCOLS, } from './constants'; 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 bb5c6c10c584b..ff93f6462915f 100644 --- a/src/plugins/navigation_embeddable/common/content_management/v1/types.ts +++ b/src/plugins/navigation_embeddable/common/content_management/v1/types.ts @@ -35,14 +35,25 @@ export type NavigationEmbeddableCrudTypes = ContentManagementCrudTypes< */ export type NavigationLinkType = typeof DASHBOARD_LINK_TYPE | typeof EXTERNAL_LINK_TYPE; -export interface NavigationEmbeddableLink { +interface BaseNavigationEmbeddableLink { id: string; - type: NavigationLinkType; - destination: string; label?: string; order: number; + destination?: string; +} + +interface DashboardLink extends BaseNavigationEmbeddableLink { + type: typeof DASHBOARD_LINK_TYPE; + destinationRefName?: string; } +interface ExternalLink extends BaseNavigationEmbeddableLink { + type: typeof EXTERNAL_LINK_TYPE; + destination: string; +} + +export type NavigationEmbeddableLink = DashboardLink | ExternalLink; + export type NavigationLayoutType = typeof NAV_HORIZONTAL_LAYOUT | typeof NAV_VERTICAL_LAYOUT; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions diff --git a/src/plugins/navigation_embeddable/common/embeddable/extract.test.ts b/src/plugins/navigation_embeddable/common/embeddable/extract.test.ts new file mode 100644 index 0000000000000..1fe746b722f8a --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/extract.test.ts @@ -0,0 +1,64 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { extract } from './extract'; + +test('Should return original state and empty references with by-reference embeddable state', () => { + const navigationEmbeddableByReferenceInput = { + id: '2192e502-0ec7-4316-82fb-c9bbf78525c4', + type: 'navigation_embeddable', + }; + + expect(extract!(navigationEmbeddableByReferenceInput)).toEqual({ + state: navigationEmbeddableByReferenceInput, + references: [], + }); +}); + +test('Should update state with refNames with by-value embeddable state', () => { + const navigationEmbeddableByValueInput = { + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + links: [ + { + type: 'dashboardLink', + id: 'fc7b8c70-2eb9-40b2-936d-457d1721a438', + destination: 'elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824', + order: 0, + }, + ], + layout: 'horizontal', + }, + type: 'navigation_embeddable', + }; + + expect(extract!(navigationEmbeddableByValueInput)).toEqual({ + references: [ + { + name: 'link_fc7b8c70-2eb9-40b2-936d-457d1721a438_dashboard', + type: 'dashboard', + id: 'elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824', + }, + ], + state: { + id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', + attributes: { + links: [ + { + type: 'dashboardLink', + id: 'fc7b8c70-2eb9-40b2-936d-457d1721a438', + destinationRefName: 'link_fc7b8c70-2eb9-40b2-936d-457d1721a438_dashboard', + order: 0, + }, + ], + layout: 'horizontal', + }, + type: 'navigation_embeddable', + }, + }); +}); diff --git a/src/plugins/navigation_embeddable/common/embeddable/extract.ts b/src/plugins/navigation_embeddable/common/embeddable/extract.ts new file mode 100644 index 0000000000000..9d0e9c0c61b13 --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/extract.ts @@ -0,0 +1,35 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { EmbeddableRegistryDefinition } from '@kbn/embeddable-plugin/common'; +import type { NavigationEmbeddableAttributes } from '../content_management'; +import { extractReferences } from '../persistable_state'; +import { NavigationEmbeddablePersistableState } from './types'; + +export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { + const typedState = state as NavigationEmbeddablePersistableState; + + // by-reference embeddable + if (!('attributes' in typedState) || typedState.attributes === undefined) { + // No references to extract for by-reference embeddable since all references are stored with by-reference saved object + return { state, references: [] }; + } + + // by-value embeddable + const { attributes, references } = extractReferences({ + attributes: typedState.attributes as unknown as NavigationEmbeddableAttributes, + }); + + return { + state: { + ...state, + attributes, + }, + references, + }; +}; diff --git a/src/plugins/navigation_embeddable/common/embeddable/index.ts b/src/plugins/navigation_embeddable/common/embeddable/index.ts new file mode 100644 index 0000000000000..c526b0bf9bff8 --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/index.ts @@ -0,0 +1,10 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export { inject } from './inject'; +export { extract } from './extract'; diff --git a/src/plugins/navigation_embeddable/common/embeddable/inject.test.ts b/src/plugins/navigation_embeddable/common/embeddable/inject.test.ts new file mode 100644 index 0000000000000..bf0d9439cb811 --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/inject.test.ts @@ -0,0 +1,69 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { inject } from './inject'; + +test('Should return original state with by-reference embeddable state', () => { + const navigationEmbeddableByReferenceInput = { + id: 'ea40fd4e-216c-49a7-917f-f733c8a2c817', + type: 'navigation_embeddable', + }; + + const references = [ + { + name: 'panel_ea40fd4e-216c-49a7-917f-f733c8a2c817', + type: 'navigation_embeddable', + id: '7f92d7d0-8e5f-11ec-9477-312c8a6de896', + }, + ]; + + expect(inject!(navigationEmbeddableByReferenceInput, references)).toEqual( + navigationEmbeddableByReferenceInput + ); +}); + +test('Should inject refNames with by-value embeddable state', () => { + const navigationEmbeddableByValueInput = { + id: 'c3937cf9-29be-43df-a4af-a4df742d7d35', + attributes: { + links: [ + { + type: 'dashboardLink', + id: 'fc7b8c70-2eb9-40b2-936d-457d1721a438', + destinationRefName: 'link_fc7b8c70-2eb9-40b2-936d-457d1721a438_dashboard', + order: 0, + }, + ], + layout: 'horizontal', + }, + type: 'navigation_embeddable', + }; + const references = [ + { + name: 'link_fc7b8c70-2eb9-40b2-936d-457d1721a438_dashboard', + type: 'dashboard', + id: 'elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824', + }, + ]; + + expect(inject!(navigationEmbeddableByValueInput, references)).toEqual({ + id: 'c3937cf9-29be-43df-a4af-a4df742d7d35', + attributes: { + links: [ + { + type: 'dashboardLink', + id: 'fc7b8c70-2eb9-40b2-936d-457d1721a438', + destination: 'elastic_agent-1a4e7280-6b5e-11ed-98de-67bdecd21824', + order: 0, + }, + ], + layout: 'horizontal', + }, + type: 'navigation_embeddable', + }); +}); diff --git a/src/plugins/navigation_embeddable/common/embeddable/inject.ts b/src/plugins/navigation_embeddable/common/embeddable/inject.ts new file mode 100644 index 0000000000000..ccf06491407ea --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/inject.ts @@ -0,0 +1,40 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { EmbeddableRegistryDefinition } from '@kbn/embeddable-plugin/common'; +import { NavigationEmbeddableAttributes } from '../content_management'; +import { injectReferences } from '../persistable_state'; +import { NavigationEmbeddablePersistableState } from './types'; + +export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { + const typedState = state as NavigationEmbeddablePersistableState; + + // by-reference embeddable + if (!('attributes' in typedState) || typedState.attributes === undefined) { + return typedState; + } + + // by-value embeddable + try { + const { attributes: attributesWithInjectedIds } = injectReferences({ + attributes: typedState.attributes as unknown as NavigationEmbeddableAttributes, + references, + }); + + return { + ...typedState, + attributes: attributesWithInjectedIds, + }; + } catch (error) { + // inject exception prevents entire dashboard from display + // Instead of throwing, swallow error and let dashboard display + // Errors will surface in navigation embeddable panel. + // Users can then manually edit links to resolve any problems. + return typedState; + } +}; diff --git a/src/plugins/navigation_embeddable/common/embeddable/types.ts b/src/plugins/navigation_embeddable/common/embeddable/types.ts new file mode 100644 index 0000000000000..80386fbfc1b2b --- /dev/null +++ b/src/plugins/navigation_embeddable/common/embeddable/types.ts @@ -0,0 +1,14 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; +import { SerializableRecord } from '@kbn/utility-types'; + +export type NavigationEmbeddablePersistableState = EmbeddableStateWithType & { + attributes: SerializableRecord; +}; diff --git a/src/plugins/navigation_embeddable/common/persistable_state/index.ts b/src/plugins/navigation_embeddable/common/persistable_state/index.ts new file mode 100644 index 0000000000000..c3e09839f0f2f --- /dev/null +++ b/src/plugins/navigation_embeddable/common/persistable_state/index.ts @@ -0,0 +1,9 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +export { extractReferences, injectReferences } from './references'; diff --git a/src/plugins/navigation_embeddable/common/persistable_state/references.test.ts b/src/plugins/navigation_embeddable/common/persistable_state/references.test.ts new file mode 100644 index 0000000000000..cf74ba929b1aa --- /dev/null +++ b/src/plugins/navigation_embeddable/common/persistable_state/references.test.ts @@ -0,0 +1,163 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { DASHBOARD_LINK_TYPE, EXTERNAL_LINK_TYPE } from '../content_management'; +import { extractReferences, injectReferences } from './references'; + +describe('extractReferences', () => { + test('should handle missing links attribute', () => { + const attributes = { + title: 'my links', + }; + expect(extractReferences({ attributes })).toEqual({ + attributes: { + title: 'my links', + }, + references: [], + }); + }); + + test('should extract dashboard references from dashboard links', () => { + const attributes = { + title: 'my links', + links: [ + { + id: 'fb1b3fc7-6e12-4542-bcf5-c61ad77241c5', + type: DASHBOARD_LINK_TYPE as typeof DASHBOARD_LINK_TYPE, + destination: '19e149f0-e95e-404b-b6f8-fc751317c6be', + order: 0, + }, + { + id: '4d5cd000-5632-4d3a-ad41-11d7800ff2aa', + type: EXTERNAL_LINK_TYPE as typeof EXTERNAL_LINK_TYPE, + destination: 'https://example.com', + order: 1, + }, + { + id: '1409fabb-1d2b-49c2-a2dc-705bd8fabd0c', + type: DASHBOARD_LINK_TYPE as typeof DASHBOARD_LINK_TYPE, + destination: '39555f99-a3b8-4210-b1ef-fa0fa86fa3da', + order: 2, + }, + ], + }; + expect(extractReferences({ attributes })).toEqual({ + attributes: { + title: 'my links', + links: [ + { + id: 'fb1b3fc7-6e12-4542-bcf5-c61ad77241c5', + type: 'dashboardLink', + destinationRefName: 'link_fb1b3fc7-6e12-4542-bcf5-c61ad77241c5_dashboard', + order: 0, + }, + { + id: '4d5cd000-5632-4d3a-ad41-11d7800ff2aa', + type: 'externalLink', + destination: 'https://example.com', + order: 1, + }, + { + id: '1409fabb-1d2b-49c2-a2dc-705bd8fabd0c', + type: 'dashboardLink', + destinationRefName: 'link_1409fabb-1d2b-49c2-a2dc-705bd8fabd0c_dashboard', + order: 2, + }, + ], + }, + references: [ + { + id: '19e149f0-e95e-404b-b6f8-fc751317c6be', + name: 'link_fb1b3fc7-6e12-4542-bcf5-c61ad77241c5_dashboard', + type: 'dashboard', + }, + { + id: '39555f99-a3b8-4210-b1ef-fa0fa86fa3da', + name: 'link_1409fabb-1d2b-49c2-a2dc-705bd8fabd0c_dashboard', + type: 'dashboard', + }, + ], + }); + }); +}); + +describe('injectReferences', () => { + test('should handle missing links attribute', () => { + const attributes = { + title: 'my links', + }; + expect(injectReferences({ attributes, references: [] })).toEqual({ + attributes: { + title: 'my links', + }, + }); + }); + + test('should inject dashboard references into dashboard links', () => { + const attributes = { + title: 'my links', + links: [ + { + id: 'fb1b3fc7-6e12-4542-bcf5-c61ad77241c5', + type: DASHBOARD_LINK_TYPE as typeof DASHBOARD_LINK_TYPE, + destinationRefName: 'link_fb1b3fc7-6e12-4542-bcf5-c61ad77241c5_dashboard', + order: 0, + }, + { + id: '4d5cd000-5632-4d3a-ad41-11d7800ff2aa', + type: EXTERNAL_LINK_TYPE as typeof EXTERNAL_LINK_TYPE, + destination: 'https://example.com', + order: 1, + }, + { + id: '1409fabb-1d2b-49c2-a2dc-705bd8fabd0c', + type: DASHBOARD_LINK_TYPE as typeof DASHBOARD_LINK_TYPE, + destinationRefName: 'link_1409fabb-1d2b-49c2-a2dc-705bd8fabd0c_dashboard', + order: 2, + }, + ], + }; + const references = [ + { + id: '19e149f0-e95e-404b-b6f8-fc751317c6be', + name: 'link_fb1b3fc7-6e12-4542-bcf5-c61ad77241c5_dashboard', + type: 'dashboard', + }, + { + id: '39555f99-a3b8-4210-b1ef-fa0fa86fa3da', + name: 'link_1409fabb-1d2b-49c2-a2dc-705bd8fabd0c_dashboard', + type: 'dashboard', + }, + ]; + expect(injectReferences({ attributes, references })).toEqual({ + attributes: { + title: 'my links', + links: [ + { + id: 'fb1b3fc7-6e12-4542-bcf5-c61ad77241c5', + type: 'dashboardLink', + destination: '19e149f0-e95e-404b-b6f8-fc751317c6be', + order: 0, + }, + { + id: '4d5cd000-5632-4d3a-ad41-11d7800ff2aa', + type: 'externalLink', + destination: 'https://example.com', + order: 1, + }, + { + id: '1409fabb-1d2b-49c2-a2dc-705bd8fabd0c', + type: 'dashboardLink', + destination: '39555f99-a3b8-4210-b1ef-fa0fa86fa3da', + order: 2, + }, + ], + }, + }); + }); +}); diff --git a/src/plugins/navigation_embeddable/common/persistable_state/references.ts b/src/plugins/navigation_embeddable/common/persistable_state/references.ts new file mode 100644 index 0000000000000..8cb0047534279 --- /dev/null +++ b/src/plugins/navigation_embeddable/common/persistable_state/references.ts @@ -0,0 +1,81 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import { Reference } from '@kbn/content-management-utils'; +import { DASHBOARD_LINK_TYPE, NavigationEmbeddableAttributes } from '../content_management'; + +export function extractReferences({ + attributes, + references = [], +}: { + attributes: NavigationEmbeddableAttributes; + references?: Reference[]; +}) { + if (!attributes.links) { + return { attributes, references }; + } + + const { links } = attributes; + const extractedReferences: Reference[] = []; + links.forEach((link) => { + if (link.type === DASHBOARD_LINK_TYPE && link.destination) { + const refName = `link_${link.id}_dashboard`; + link.destinationRefName = refName; + extractedReferences.push({ + name: refName, + type: 'dashboard', + id: link.destination, + }); + delete link.destination; + } + }); + + return { + attributes: { + ...attributes, + links, + }, + references: references.concat(extractedReferences), + }; +} + +function findReference(targetName: string, references: Reference[]) { + const reference = references.find(({ name }) => name === targetName); + if (!reference) { + throw new Error(`Could not find reference "${targetName}"`); + } + return reference; +} + +export function injectReferences({ + attributes, + references, +}: { + attributes: NavigationEmbeddableAttributes; + references: Reference[]; +}) { + if (!attributes.links) { + return { attributes }; + } + + const { links } = attributes; + links.forEach((link) => { + if (link.type === DASHBOARD_LINK_TYPE && link.destinationRefName) { + const reference = findReference(link.destinationRefName, references); + link.destination = reference.id; + delete link.destinationRefName; + } + }); + + return { + attributes: { + ...attributes, + links, + }, + }; +} diff --git a/src/plugins/navigation_embeddable/jest.config.js b/src/plugins/navigation_embeddable/jest.config.js new file mode 100644 index 0000000000000..d864b4a234669 --- /dev/null +++ b/src/plugins/navigation_embeddable/jest.config.js @@ -0,0 +1,18 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/navigation_embeddable'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/navigation_embeddable', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/navigation_embeddable/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx index 8259d98cce4b6..00f12d65b36c1 100644 --- a/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx +++ b/src/plugins/navigation_embeddable/public/components/dashboard_link/dashboard_link_component.tsx @@ -42,7 +42,7 @@ export const DashboardLinkComponent = ({ const { loading: loadingDestinationDashboard, value: destinationDashboard } = useAsync(async () => { - if (link.id !== parentDashboardId) { + if (link.id !== parentDashboardId && link.destination) { /** * only fetch the dashboard if it's not the current dashboard - if it is the current dashboard, * use `dashboardContainer` and its corresponding state (title, description, etc.) instead. 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 3226833f1f9c8..32aab131ddb1b 100644 --- a/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts +++ b/src/plugins/navigation_embeddable/public/embeddable/navigation_embeddable_factory.ts @@ -12,7 +12,6 @@ import { ErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; import { lazyLoadReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; -import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; import { NavigationEmbeddableByReferenceInput, NavigationEmbeddableInput } from './types'; @@ -20,6 +19,7 @@ import { APP_ICON, APP_NAME, CONTENT_ID } from '../../common'; import type { NavigationEmbeddable } from './navigation_embeddable'; import { getNavigationEmbeddableAttributeService } from '../services/attribute_service'; import { coreServices, untilPluginStartServicesReady } from '../services/kibana_services'; +import { extract, inject } from '../../common/embeddable'; export type NavigationEmbeddableFactory = EmbeddableFactory; @@ -41,15 +41,6 @@ export class NavigationEmbeddableFactoryDefinition getIconForSavedObject: () => APP_ICON, }; - // TODO create functions - // public inject: EmbeddablePersistableStateService['inject']; - // public extract: EmbeddablePersistableStateService['extract']; - - constructor(persistableStateService: EmbeddablePersistableStateService) { - // this.inject = createInject(this.persistableStateService); - // this.extract = createExtract(this.persistableStateService); - } - public async isEditable() { await untilPluginStartServicesReady(); return Boolean(coreServices.application.capabilities.dashboard?.showWriteControls); @@ -116,4 +107,8 @@ export class NavigationEmbeddableFactoryDefinition public getIconType() { return 'link'; } + + inject = inject; + + extract = extract; } diff --git a/src/plugins/navigation_embeddable/public/services/attribute_service.ts b/src/plugins/navigation_embeddable/public/services/attribute_service.ts index 7a7dbfe2bd139..74530871fc469 100644 --- a/src/plugins/navigation_embeddable/public/services/attribute_service.ts +++ b/src/plugins/navigation_embeddable/public/services/attribute_service.ts @@ -11,6 +11,7 @@ import { AttributeService } from '@kbn/embeddable-plugin/public'; import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import { SharingSavedObjectProps } from '../../common/types'; import { NavigationEmbeddableAttributes } from '../../common/content_management'; +import { extractReferences, injectReferences } from '../../common/persistable_state'; import { NavigationEmbeddableByReferenceInput, NavigationEmbeddableByValueInput, @@ -45,12 +46,19 @@ export function getNavigationEmbeddableAttributeService(): NavigationEmbeddableA NavigationEmbeddableUnwrapMetaInfo >(CONTENT_ID, { saveMethod: async (attributes: NavigationEmbeddableDocument, savedObjectId?: string) => { - // TODO extract references + const { attributes: updatedAttributes, references } = extractReferences({ + attributes, + references: attributes.references, + }); const { item: { id }, } = await (savedObjectId - ? navigationEmbeddableClient.update({ id: savedObjectId, data: attributes }) - : navigationEmbeddableClient.create({ data: attributes, options: { references: [] } })); + ? navigationEmbeddableClient.update({ + id: savedObjectId, + data: updatedAttributes, + options: { references }, + }) + : navigationEmbeddableClient.create({ data: updatedAttributes, options: { references } })); return { id }; }, unwrapMethod: async ( @@ -65,8 +73,7 @@ export function getNavigationEmbeddableAttributeService(): NavigationEmbeddableA } = await navigationEmbeddableClient.get(savedObjectId); if (savedObject.error) throw savedObject.error; - // TODO inject references - const attributes = savedObject.attributes; + const { attributes } = injectReferences(savedObject); return { attributes, metaInfo: { diff --git a/src/plugins/navigation_embeddable/tsconfig.json b/src/plugins/navigation_embeddable/tsconfig.json index ae66fa7cc3144..afcc5945c2082 100644 --- a/src/plugins/navigation_embeddable/tsconfig.json +++ b/src/plugins/navigation_embeddable/tsconfig.json @@ -20,6 +20,7 @@ "@kbn/saved-objects-plugin", "@kbn/core-saved-objects-server", "@kbn/saved-objects-plugin", + "@kbn/utility-types", ], "exclude": ["target/**/*"] }