diff --git a/examples/embeddable_examples/server/plugin.ts b/examples/embeddable_examples/server/plugin.ts index d34ea405d3a48..15551821d3a44 100644 --- a/examples/embeddable_examples/server/plugin.ts +++ b/examples/embeddable_examples/server/plugin.ts @@ -39,7 +39,9 @@ export class EmbeddableExamplesPlugin implements Plugin bookTransforms, + }); return {}; } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 179c5298f2621..cd5bcf0ee587d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -30,7 +30,7 @@ pageLoadAssetSize: cps: 5918 crossClusterReplication: 12662 customIntegrations: 11715 - dashboard: 19000 + dashboard: 19957 dashboardEnhanced: 12159 dashboardMarkdown: 4994 data: 500179 @@ -50,9 +50,9 @@ pageLoadAssetSize: discoverShared: 2322 elasticAssistant: 338870 elasticAssistantSharedState: 4881 - embeddable: 18432 + embeddable: 17521 embeddableAlertsTable: 6524 - embeddableEnhanced: 7912 + embeddableEnhanced: 4488 enterpriseSearch: 40244 entityStore: 1755 esql: 18224 @@ -83,7 +83,7 @@ pageLoadAssetSize: graph: 9924 grokdebugger: 5484 home: 13560 - imageEmbeddable: 6000 + imageEmbeddable: 6727 indexLifecycleManagement: 29818 indexManagement: 35522 inference: 10368 diff --git a/src/platform/plugins/private/image_embeddable/common/constants.ts b/src/platform/plugins/private/image_embeddable/common/constants.ts index b32c78ec970aa..9338f899b2eea 100644 --- a/src/platform/plugins/private/image_embeddable/common/constants.ts +++ b/src/platform/plugins/private/image_embeddable/common/constants.ts @@ -9,3 +9,5 @@ export const IMAGE_EMBEDDABLE_TYPE = 'image'; export const ADD_IMAGE_EMBEDDABLE_ACTION_ID = 'create_image_embeddable'; +export const IMAGE_CLICK_TRIGGER = 'IMAGE_CLICK_TRIGGER'; +export const IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS = [IMAGE_CLICK_TRIGGER]; diff --git a/src/platform/plugins/private/image_embeddable/common/index.ts b/src/platform/plugins/private/image_embeddable/common/index.ts new file mode 100644 index 0000000000000..71816832d11a3 --- /dev/null +++ b/src/platform/plugins/private/image_embeddable/common/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", 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". + */ + +export { IMAGE_CLICK_TRIGGER, IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS } from './constants'; diff --git a/src/platform/plugins/private/image_embeddable/common/transforms.ts b/src/platform/plugins/private/image_embeddable/common/transforms.ts index b0b6d7af0b72d..8d63a465e155b 100644 --- a/src/platform/plugins/private/image_embeddable/common/transforms.ts +++ b/src/platform/plugins/private/image_embeddable/common/transforms.ts @@ -7,41 +7,22 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ import type { Reference } from '@kbn/content-management-utils'; -import type { - TransformEnhancementsIn, - TransformEnhancementsOut, -} from '@kbn/embeddable-plugin/common'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { transformTitlesOut } from '@kbn/presentation-publishing'; +import { flow } from 'lodash'; import type { ImageEmbeddableState } from '../server'; -export function getTransforms( - transformEnhancementsIn: TransformEnhancementsIn, - transformEnhancementsOut: TransformEnhancementsOut -) { +export function getTransforms(drilldownTransforms: DrilldownTransforms) { return { transformIn: (state: ImageEmbeddableState) => { - const enhancementResult = state.enhancements - ? transformEnhancementsIn(state.enhancements) - : { state: undefined, references: [] }; - - return { - state: { - ...state, - ...(enhancementResult.state ? { enhancements: enhancementResult.state } : {}), - }, - references: enhancementResult.references, - }; + return drilldownTransforms.transformIn(state); }, transformOut: (storedState: ImageEmbeddableState, references?: Reference[]) => { - const state = transformTitlesOut(storedState); - const enhancementsState = state.enhancements - ? transformEnhancementsOut(state.enhancements, references ?? []) - : undefined; - - return { - ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), - }; + const transformsFlow = flow( + transformTitlesOut, + (state: ImageEmbeddableState) => drilldownTransforms.transformOut(state, references) + ); + return transformsFlow(storedState); }, }; } diff --git a/src/platform/plugins/private/image_embeddable/kibana.jsonc b/src/platform/plugins/private/image_embeddable/kibana.jsonc index 3f34ad589f3d4..9754b9df252c9 100644 --- a/src/platform/plugins/private/image_embeddable/kibana.jsonc +++ b/src/platform/plugins/private/image_embeddable/kibana.jsonc @@ -22,6 +22,7 @@ "screenshotMode", "embeddableEnhanced" ], - "requiredBundles": [] + "requiredBundles": [], + "extraPublicDirs": ["common"] } } \ No newline at end of file diff --git a/src/platform/plugins/private/image_embeddable/moon.yml b/src/platform/plugins/private/image_embeddable/moon.yml index fe1c29a9f3744..52b708a286937 100644 --- a/src/platform/plugins/private/image_embeddable/moon.yml +++ b/src/platform/plugins/private/image_embeddable/moon.yml @@ -48,8 +48,8 @@ tags: - jest-unit-tests fileGroups: src: - - public/**/* - common/**/* + - public/**/* - server/**/* - '!target/**/*' tasks: diff --git a/src/platform/plugins/private/image_embeddable/public/actions/image_click_trigger.ts b/src/platform/plugins/private/image_embeddable/public/actions/image_click_trigger.ts index 75acb254cd55c..49f0b0ff1f7f8 100644 --- a/src/platform/plugins/private/image_embeddable/public/actions/image_click_trigger.ts +++ b/src/platform/plugins/private/image_embeddable/public/actions/image_click_trigger.ts @@ -9,8 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { Trigger } from '@kbn/ui-actions-plugin/public'; - -export const IMAGE_CLICK_TRIGGER = 'IMAGE_CLICK_TRIGGER'; +import { IMAGE_CLICK_TRIGGER } from '../../common'; export const imageClickTrigger: Trigger = { id: IMAGE_CLICK_TRIGGER, diff --git a/src/platform/plugins/private/image_embeddable/public/actions/index.ts b/src/platform/plugins/private/image_embeddable/public/actions/index.ts index eea9752e53811..9616a5a6a0716 100644 --- a/src/platform/plugins/private/image_embeddable/public/actions/index.ts +++ b/src/platform/plugins/private/image_embeddable/public/actions/index.ts @@ -7,4 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export * from './image_click_trigger'; +export { imageClickTrigger } from './image_click_trigger'; diff --git a/src/platform/plugins/private/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx b/src/platform/plugins/private/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx index 6e0dc55f9f334..2afb36b58483b 100644 --- a/src/platform/plugins/private/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx +++ b/src/platform/plugins/private/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx @@ -18,11 +18,10 @@ import { openLazyFlyout } from '@kbn/presentation-util'; import { initializeTitleManager, titleComparators } from '@kbn/presentation-publishing'; import type { ImageEmbeddableState } from '../../server'; -import { IMAGE_CLICK_TRIGGER } from '../actions'; import { ImageEmbeddable as ImageEmbeddableComponent } from '../components/image_embeddable'; import type { FileImageMetadata } from '../imports'; import { coreServices, filesService } from '../services/kibana_services'; -import { IMAGE_EMBEDDABLE_TYPE } from '../../common/constants'; +import { IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS, IMAGE_EMBEDDABLE_TYPE } from '../../common/constants'; import type { ImageConfig, ImageEmbeddableApi } from '../types'; export const getImageEmbeddableFactory = ({ @@ -35,7 +34,7 @@ export const getImageEmbeddableFactory = ({ buildEmbeddable: async ({ initialState, finalizeApi, uuid, parentApi }) => { const titleManager = initializeTitleManager(initialState); - const dynamicActionsManager = embeddableEnhanced?.initializeEmbeddableDynamicActions( + const dynamicActionsManager = await embeddableEnhanced?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -66,7 +65,7 @@ export const getImageEmbeddableFactory = ({ ), getComparators: () => { return { - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip', drilldowns: 'skip' }), ...titleComparators, imageConfig: 'deepEquality', }; @@ -83,7 +82,7 @@ export const getImageEmbeddableFactory = ({ ...(dynamicActionsManager?.api ?? {}), ...unsavedChangesApi, dataLoading$, - supportedTriggers: () => [IMAGE_CLICK_TRIGGER], + supportedTriggers: () => IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS, onEdit: async () => { openLazyFlyout({ diff --git a/src/platform/plugins/private/image_embeddable/public/index.ts b/src/platform/plugins/private/image_embeddable/public/index.ts index e86825454d0a8..49942f13c2b07 100644 --- a/src/platform/plugins/private/image_embeddable/public/index.ts +++ b/src/platform/plugins/private/image_embeddable/public/index.ts @@ -9,7 +9,7 @@ import { ImageEmbeddablePlugin } from './plugin'; -export { IMAGE_CLICK_TRIGGER } from './actions'; +export { IMAGE_CLICK_TRIGGER } from '../common'; export function plugin() { return new ImageEmbeddablePlugin(); diff --git a/src/platform/plugins/private/image_embeddable/server/plugin.ts b/src/platform/plugins/private/image_embeddable/server/plugin.ts index 92547710ec507..b998486fbc8ba 100644 --- a/src/platform/plugins/private/image_embeddable/server/plugin.ts +++ b/src/platform/plugins/private/image_embeddable/server/plugin.ts @@ -15,10 +15,7 @@ import type { SetupDeps, StartDeps } from './types'; export class ImageEmbeddablePlugin implements Plugin { setup(core: CoreSetup, plugins: SetupDeps) { plugins.embeddable.registerTransforms(IMAGE_EMBEDDABLE_TYPE, { - ...getTransforms( - plugins.embeddable.transformEnhancementsIn, - plugins.embeddable.transformEnhancementsOut - ), + getTransforms, // TODO register schema when its ready to be public }); } diff --git a/src/platform/plugins/private/image_embeddable/server/schemas.ts b/src/platform/plugins/private/image_embeddable/server/schemas.ts index 4b7f81850c300..ef86d19ebcc1b 100644 --- a/src/platform/plugins/private/image_embeddable/server/schemas.ts +++ b/src/platform/plugins/private/image_embeddable/server/schemas.ts @@ -9,7 +9,9 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; +import type { GetDrilldownsSchemaFnType } from '@kbn/embeddable-plugin/server'; import { serializedTitlesSchema } from '@kbn/presentation-publishing-schemas'; +import { IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS } from '../common'; const imageFileSrcSchema = schema.object({ type: schema.literal('file'), @@ -55,23 +57,25 @@ const imageConfigSchema = schema.object({ backgroundColor: schema.maybe(schema.string()), }); -export const imageEmbeddableSchema = schema.allOf( - [ - schema.object({ - imageConfig: imageConfigSchema, - enhancements: schema.maybe(schema.any()), - }), - serializedTitlesSchema, - ], - { - meta: { - description: 'Image embeddable schema', - }, - } -); +export function getImageEmbeddableSchema(getDrilldownsSchemas: GetDrilldownsSchemaFnType) { + return schema.allOf( + [ + getDrilldownsSchemas(IMAGE_EMBEDDABLE_SUPPORTED_TRIGGERS), + schema.object({ + imageConfig: imageConfigSchema, + }), + serializedTitlesSchema, + ], + { + meta: { + description: 'Image embeddable schema', + }, + } + ); +} // TODO - snake_caseify all of these schemas export type ImageConfig = TypeOf; export type ImageConfigState = TypeOf; -export type ImageEmbeddableState = TypeOf; +export type ImageEmbeddableState = TypeOf>; diff --git a/src/platform/plugins/private/image_embeddable/tsconfig.json b/src/platform/plugins/private/image_embeddable/tsconfig.json index b522348b6ba43..e2af6350e084a 100644 --- a/src/platform/plugins/private/image_embeddable/tsconfig.json +++ b/src/platform/plugins/private/image_embeddable/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["public/**/*", "common/**/*", "server/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", "@kbn/embeddable-plugin", diff --git a/src/platform/plugins/private/links/common/embeddable/transforms/get_options.ts b/src/platform/plugins/private/links/common/embeddable/transforms/get_options.ts index 1bbd8ecfeef40..80b670fdef9e1 100644 --- a/src/platform/plugins/private/links/common/embeddable/transforms/get_options.ts +++ b/src/platform/plugins/private/links/common/embeddable/transforms/get_options.ts @@ -40,7 +40,7 @@ export function getOptions(type: LinkType, options: LinkOptions) { 'boolean' && { use_time_range: (dashboardOptions as { useCurrentDateRange?: boolean }).useCurrentDateRange, }), - }; + } as DashboardLink['options']; } const urlOptions = options as Required['options']; diff --git a/src/platform/plugins/private/links/server/plugin.ts b/src/platform/plugins/private/links/server/plugin.ts index de040b4807bef..bada80a727619 100644 --- a/src/platform/plugins/private/links/server/plugin.ts +++ b/src/platform/plugins/private/links/server/plugin.ts @@ -50,7 +50,7 @@ export class LinksServerPlugin implements Plugin { core.savedObjects.registerType(linksSavedObjectType); plugins.embeddable.registerTransforms(LINKS_EMBEDDABLE_TYPE, { - ...transforms, + getTransforms: () => transforms, // TODO register schema when its ready to be public }); diff --git a/src/platform/plugins/shared/controls/common/transforms/data_controls/register_data_control_transforms.ts b/src/platform/plugins/shared/controls/common/transforms/data_controls/register_data_control_transforms.ts index 63bd39e288c1c..a84138cd214a2 100644 --- a/src/platform/plugins/shared/controls/common/transforms/data_controls/register_data_control_transforms.ts +++ b/src/platform/plugins/shared/controls/common/transforms/data_controls/register_data_control_transforms.ts @@ -17,8 +17,10 @@ export const registerDataControlTransforms = ( legacyRefNames: string[] ) => { embeddable.registerTransforms(type, { - transformIn: (state) => extractReferences(state, refName), - transformOut: (state, panelReferences, containerReferences, id) => - injectReferences(id, state, legacyRefNames, panelReferences, containerReferences), + getTransforms: () => ({ + transformIn: (state) => extractReferences(state, refName), + transformOut: (state, panelReferences, containerReferences, id) => + injectReferences(id, state, legacyRefNames, panelReferences, containerReferences), + }), }); }; diff --git a/src/platform/plugins/shared/dashboard/common/page_bundle_constants.ts b/src/platform/plugins/shared/dashboard/common/page_bundle_constants.ts index 0c7ed21ee8f5f..4bb407030eaee 100644 --- a/src/platform/plugins/shared/dashboard/common/page_bundle_constants.ts +++ b/src/platform/plugins/shared/dashboard/common/page_bundle_constants.ts @@ -19,3 +19,6 @@ export const DEFAULT_DASHBOARD_NAVIGATION_OPTIONS = { use_time_range: true, use_filters: true, }; + +// Do not change constant value - part of dashboard REST API +export const DASHBOARD_DRILLDOWN_TYPE = 'dashboard_drilldown'; diff --git a/src/platform/plugins/shared/dashboard/moon.yml b/src/platform/plugins/shared/dashboard/moon.yml index b3bde5b339d7f..7ebaf6e8ec323 100644 --- a/src/platform/plugins/shared/dashboard/moon.yml +++ b/src/platform/plugins/shared/dashboard/moon.yml @@ -113,6 +113,7 @@ dependsOn: - '@kbn/core-chrome-app-menu-components' - '@kbn/core-chrome-app-menu' - '@kbn/test-jest-helpers' + - '@kbn/image-embeddable-plugin' tags: - plugin - prod diff --git a/src/platform/plugins/shared/dashboard/server/api/dashboard_state_schemas.ts b/src/platform/plugins/shared/dashboard/server/api/dashboard_state_schemas.ts index 1d073ca1075ec..8df10b55dba34 100644 --- a/src/platform/plugins/shared/dashboard/server/api/dashboard_state_schemas.ts +++ b/src/platform/plugins/shared/dashboard/server/api/dashboard_state_schemas.ts @@ -52,7 +52,9 @@ export const panelGridSchema = schema.object({ export function getPanelSchema() { return schema.object({ config: schema.oneOf([ - ...((embeddableService ? embeddableService.getEmbeddableSchemas() : []) as [ObjectType<{}>]), + ...((embeddableService ? embeddableService.getAllEmbeddableSchemas() : []) as [ + ObjectType<{}> + ]), schema.object( {}, { diff --git a/src/platform/plugins/shared/dashboard/server/api/scope_tooling.test.ts b/src/platform/plugins/shared/dashboard/server/api/scope_tooling.test.ts index 9ed4dcc41bb11..7659bce086ea2 100644 --- a/src/platform/plugins/shared/dashboard/server/api/scope_tooling.test.ts +++ b/src/platform/plugins/shared/dashboard/server/api/scope_tooling.test.ts @@ -28,10 +28,9 @@ describe('stripUnmappedKeys', () => { it('should not drop mapped panel types', () => { mockGetTransforms.mockImplementation(() => { return { - getSchema: () => - schema.object({ - foo: schema.string(), - }), + schema: schema.object({ + foo: schema.string(), + }), }; }); const dashboardState = { @@ -110,7 +109,7 @@ describe('stripUnmappedKeys', () => { it('should drop unmapped panel types when throwOnUnmappedPanel throws', () => { mockGetTransforms.mockImplementation(() => { return { - getSchema: () => schema.any(), + schema: schema.any(), throwOnUnmappedPanel: () => { throw new Error('Unmapped panel type'); }, @@ -150,10 +149,9 @@ describe('stripUnmappedKeys', () => { it('should drop panel enhancements', () => { mockGetTransforms.mockImplementation(() => { return { - getSchema: () => - schema.object({ - foo: schema.string(), - }), + schema: schema.object({ + foo: schema.string(), + }), }; }); const dashboardState = { @@ -274,10 +272,9 @@ describe('throwOnUnmappedKeys', () => { it('should not throw when there are no unmapped keys', () => { mockGetTransforms.mockImplementation(() => { return { - getSchema: () => - schema.object({ - foo: schema.string(), - }), + schema: schema.object({ + foo: schema.string(), + }), }; }); const dashboardState = { @@ -324,7 +321,7 @@ describe('throwOnUnmappedKeys', () => { it('should throw when dashboard contains a panel with enhancements', () => { mockGetTransforms.mockImplementation(() => { return { - getSchema: () => schema.object({}), + schema: schema.object({}), }; }); const dashboardState = { diff --git a/src/platform/plugins/shared/dashboard/server/api/scope_tooling.ts b/src/platform/plugins/shared/dashboard/server/api/scope_tooling.ts index f63375842a4ea..fc0e72dd23844 100644 --- a/src/platform/plugins/shared/dashboard/server/api/scope_tooling.ts +++ b/src/platform/plugins/shared/dashboard/server/api/scope_tooling.ts @@ -31,7 +31,7 @@ export function stripUnmappedKeys(dashboardState: DashboardState) { } } - const panelSchema = transforms?.getSchema?.(); + const panelSchema = transforms?.schema; if (!panelSchema) { warnings.push( @@ -85,7 +85,7 @@ export function throwOnUnmappedKeys(dashboardState: DashboardState) { function throwOnUnmappedPanelKeys(panel: DashboardPanel) { const transforms = embeddableService?.getTransforms(panel.type); - const panelSchema = transforms?.getSchema?.(); + const panelSchema = transforms?.schema; if (!panelSchema) { throw new Error( diff --git a/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.test.ts b/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.test.ts index 42dccb9e56bdf..ee1e737e0c54e 100644 --- a/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.test.ts +++ b/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.test.ts @@ -113,7 +113,7 @@ describe('transformPanelsIn', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-var-requires require('../../../kibana_services').embeddableService = { - getTransforms: () => ({ getSchema: () => TestEmbeddableSchema }), + getTransforms: () => ({ schema: TestEmbeddableSchema }), }; }); diff --git a/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.ts b/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.ts index 5f1e8b076841b..912316f61009a 100644 --- a/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.ts +++ b/src/platform/plugins/shared/dashboard/server/api/transforms/in/transform_panels_in.ts @@ -60,7 +60,7 @@ function transformPanelIn(panel: DashboardPanel): { const idx = uid ?? uuidv4(); const transforms = embeddableService?.getTransforms(panel.type); - const panelSchema = transforms?.getSchema?.(); + const panelSchema = transforms?.schema; if (panelSchema) { try { diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/register_dashboard_drilldown.ts b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/register_dashboard_drilldown.ts new file mode 100644 index 0000000000000..7711a64232da1 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/register_dashboard_drilldown.ts @@ -0,0 +1,28 @@ +/* + * 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 { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/common'; +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { IMAGE_CLICK_TRIGGER } from '@kbn/image-embeddable-plugin/common'; +import { DASHBOARD_DRILLDOWN_TYPE } from '../../common/page_bundle_constants'; +import { transformIn, transformOut } from './transforms'; +import { dashboardDrilldownSchema } from './schemas'; +import type { DashboardDrilldown, StoredDashboardDrilldown } from './types'; + +export function registerDashboardDrilldown(embeddableSetup: EmbeddableSetup) { + embeddableSetup.registerDrilldown( + DASHBOARD_DRILLDOWN_TYPE, + { + schema: dashboardDrilldownSchema, + supportedTriggers: [APPLY_FILTER_TRIGGER, IMAGE_CLICK_TRIGGER], + transformIn, + transformOut, + } + ); +} diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/schemas.ts b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/schemas.ts new file mode 100644 index 0000000000000..40a11148f6ef7 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/schemas.ts @@ -0,0 +1,15 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { dashboardNavigationOptionsSchema } from '../dashboard_navigation'; + +export const dashboardDrilldownSchema = dashboardNavigationOptionsSchema.extends({ + dashboard_id: schema.string(), +}); diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/transforms.ts b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/transforms.ts new file mode 100644 index 0000000000000..ba4d16801ffae --- /dev/null +++ b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/transforms.ts @@ -0,0 +1,44 @@ +/* + * 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 { Reference } from '@kbn/content-management-utils'; +import type { DashboardDrilldown, StoredDashboardDrilldown } from './types'; +import { DASHBOARD_SAVED_OBJECT_TYPE } from '../../common/constants'; + +export function transformIn(state: DashboardDrilldown): { + state: StoredDashboardDrilldown; + references?: Reference[]; +} { + const { dashboard_id, ...rest } = state; + const dashboardRef = { + type: DASHBOARD_SAVED_OBJECT_TYPE, + // Using dashboard id to avoid + name: `dashboard_drilldown_${dashboard_id}`, + id: dashboard_id, + }; + return { + state: { + ...rest, + dashboardRefName: dashboardRef.name, + }, + references: [dashboardRef], + }; +} + +export function transformOut( + storedState: StoredDashboardDrilldown, + references?: Reference[] +): DashboardDrilldown { + const { dashboardRefName, ...rest } = storedState; + const reference = references?.find(({ name }) => name === dashboardRefName); + return { + ...rest, + dashboard_id: reference?.id ?? '', + }; +} diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/types.ts b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/types.ts new file mode 100644 index 0000000000000..cd95e1d498f65 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/server/dashboard_drilldown/types.ts @@ -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", 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 { DrilldownState } from '@kbn/embeddable-plugin/server'; +import type { TypeOf } from '@kbn/config-schema'; +import type { dashboardDrilldownSchema } from './schemas'; + +export type DashboardDrilldown = DrilldownState & TypeOf; + +export type StoredDashboardDrilldown = Omit & { + dashboardRefName: string; +}; diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_navigation/schemas.ts b/src/platform/plugins/shared/dashboard/server/dashboard_navigation/schemas.ts index a8c1c1ec141ef..38650c5cc07fc 100644 --- a/src/platform/plugins/shared/dashboard/server/dashboard_navigation/schemas.ts +++ b/src/platform/plugins/shared/dashboard/server/dashboard_navigation/schemas.ts @@ -11,28 +11,22 @@ import { schema } from '@kbn/config-schema'; import { DEFAULT_DASHBOARD_NAVIGATION_OPTIONS } from '../../common/page_bundle_constants'; export const dashboardNavigationOptionsSchema = schema.object({ - use_filters: schema.maybe( - schema.boolean({ - defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.use_filters, - meta: { - description: 'When enabled, filters are passed to the opening dashboard.', - }, - }) - ), - use_time_range: schema.maybe( - schema.boolean({ - defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.use_time_range, - meta: { - description: 'When enabled, time range is passed to the opening dashboard.', - }, - }) - ), - open_in_new_tab: schema.maybe( - schema.boolean({ - defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.open_in_new_tab, - meta: { - description: 'When enabled, the dashboard opens in a new browser tab.', - }, - }) - ), + use_filters: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.use_filters, + meta: { + description: 'When enabled, filters are passed to the opening dashboard.', + }, + }), + use_time_range: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.use_time_range, + meta: { + description: 'When enabled, time range is passed to the opening dashboard.', + }, + }), + open_in_new_tab: schema.boolean({ + defaultValue: DEFAULT_DASHBOARD_NAVIGATION_OPTIONS.open_in_new_tab, + meta: { + description: 'When enabled, the dashboard opens in a new browser tab.', + }, + }), }); diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_navigation/types.ts b/src/platform/plugins/shared/dashboard/server/dashboard_navigation/types.ts index e49da22457934..f08aa5d7e8b23 100644 --- a/src/platform/plugins/shared/dashboard/server/dashboard_navigation/types.ts +++ b/src/platform/plugins/shared/dashboard/server/dashboard_navigation/types.ts @@ -10,4 +10,4 @@ import type { TypeOf } from '@kbn/config-schema'; import type { dashboardNavigationOptionsSchema } from './schemas'; -export type DashboardNavigationOptions = Required>; +export type DashboardNavigationOptions = TypeOf; diff --git a/src/platform/plugins/shared/dashboard/server/plugin.ts b/src/platform/plugins/shared/dashboard/server/plugin.ts index 5dcc57885c165..417328ebc6be9 100644 --- a/src/platform/plugins/shared/dashboard/server/plugin.ts +++ b/src/platform/plugins/shared/dashboard/server/plugin.ts @@ -49,6 +49,7 @@ import { registerRoutes, create, read, update, deleteDashboard } from './api'; import { DashboardAppLocatorDefinition } from '../common/locator/locator'; import { setKibanaServices } from './kibana_services'; import { scanDashboards } from './scan_dashboards'; +import { registerDashboardDrilldown } from './dashboard_drilldown/register_dashboard_drilldown'; interface SetupDeps { embeddable: EmbeddableSetup; @@ -136,6 +137,8 @@ export class DashboardPlugin })), }); + registerDashboardDrilldown(plugins.embeddable); + return {}; } diff --git a/src/platform/plugins/shared/dashboard/tsconfig.json b/src/platform/plugins/shared/dashboard/tsconfig.json index 1e83168757552..48efab4d1c885 100644 --- a/src/platform/plugins/shared/dashboard/tsconfig.json +++ b/src/platform/plugins/shared/dashboard/tsconfig.json @@ -99,7 +99,8 @@ "@kbn/cps", "@kbn/core-chrome-app-menu-components", "@kbn/core-chrome-app-menu", - "@kbn/test-jest-helpers" + "@kbn/test-jest-helpers", + "@kbn/image-embeddable-plugin", ], "exclude": ["target/**/*"] } diff --git a/src/platform/plugins/shared/data/common/constants.ts b/src/platform/plugins/shared/data/common/constants.ts index 8696dccfe0c00..1f6d6d635e042 100644 --- a/src/platform/plugins/shared/data/common/constants.ts +++ b/src/platform/plugins/shared/data/common/constants.ts @@ -18,3 +18,5 @@ export const SAVED_QUERY_BASE_URL = '/internal/saved_query'; export const KQL_TELEMETRY_ROUTE_LATEST_VERSION = '1'; export const SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION = '1'; + +export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; diff --git a/src/platform/plugins/shared/data/common/index.ts b/src/platform/plugins/shared/data/common/index.ts index 92c297fc0a05a..185eeefba85c2 100644 --- a/src/platform/plugins/shared/data/common/index.ts +++ b/src/platform/plugins/shared/data/common/index.ts @@ -9,6 +9,7 @@ export type { RefreshInterval } from '@kbn/data-service-server'; export { + APPLY_FILTER_TRIGGER, DEFAULT_QUERY_LANGUAGE, KIBANA_USER_QUERY_LANGUAGE_KEY, KQL_TELEMETRY_ROUTE_LATEST_VERSION, diff --git a/src/platform/plugins/shared/data/public/triggers/apply_filter_trigger.ts b/src/platform/plugins/shared/data/public/triggers/apply_filter_trigger.ts index 6a987ef3e6160..372010f26124e 100644 --- a/src/platform/plugins/shared/data/public/triggers/apply_filter_trigger.ts +++ b/src/platform/plugins/shared/data/public/triggers/apply_filter_trigger.ts @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import type { Trigger } from '@kbn/ui-actions-plugin/public'; +import { APPLY_FILTER_TRIGGER } from '../../common'; -export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; export const applyFilterTrigger: Trigger = { id: APPLY_FILTER_TRIGGER, title: i18n.translate('data.triggers.applyFilterTitle', { diff --git a/src/platform/plugins/shared/data/public/triggers/index.ts b/src/platform/plugins/shared/data/public/triggers/index.ts index 49f76d58da374..ea41cf8b0868f 100644 --- a/src/platform/plugins/shared/data/public/triggers/index.ts +++ b/src/platform/plugins/shared/data/public/triggers/index.ts @@ -7,4 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export * from './apply_filter_trigger'; +export { APPLY_FILTER_TRIGGER } from '../../common'; +export { applyFilterTrigger } from './apply_filter_trigger'; diff --git a/src/platform/plugins/shared/discover/common/embeddable/get_transform_in.ts b/src/platform/plugins/shared/discover/common/embeddable/get_transform_in.ts index 1beece1a7f0d6..bac4daa26e39c 100644 --- a/src/platform/plugins/shared/discover/common/embeddable/get_transform_in.ts +++ b/src/platform/plugins/shared/discover/common/embeddable/get_transform_in.ts @@ -9,12 +9,10 @@ import { SavedSearchType } from '@kbn/saved-search-plugin/common'; import type { SavedObjectReference } from '@kbn/core/server'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { SearchEmbeddableByReferenceState, SearchEmbeddableState, - StoredSearchEmbeddableByReferenceState, - StoredSearchEmbeddableByValueState, StoredSearchEmbeddableState, } from './types'; import { extract } from './search_inject_extract'; @@ -25,36 +23,25 @@ function isByRefState(state: SearchEmbeddableState): state is SearchEmbeddableBy return 'savedObjectId' in state; } -export function getTransformIn( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'] -) { +export function getTransformIn(transformDrilldownsIn: DrilldownTransforms['transformIn']) { function transformIn(state: SearchEmbeddableState): { state: StoredSearchEmbeddableState; references: SavedObjectReference[]; } { - const enhancementsResult = state.enhancements - ? transformEnhancementsIn(state.enhancements) - : { state: undefined, references: [] }; + const { state: storedState, references: drilldownReferences } = + transformDrilldownsIn(state); - if (isByRefState(state)) { - const { savedObjectId, ...rest } = state; + if (isByRefState(storedState)) { + const { savedObjectId, ...rest } = storedState; return { - state: { - ...rest, - ...(enhancementsResult.state - ? { - enhancements: - enhancementsResult.state as StoredSearchEmbeddableByReferenceState['enhancements'], - } - : {}), - }, + state: rest, references: [ { name: SAVED_SEARCH_SAVED_OBJECT_REF_NAME, type: SavedSearchType, id: savedObjectId, }, - ...enhancementsResult.references, + ...drilldownReferences, ], }; } @@ -62,26 +49,20 @@ export function getTransformIn( // by value const { state: extractedState, references } = extract({ type: SavedSearchType, - attributes: state.attributes, + attributes: storedState.attributes, }); return { state: { - ...state, - ...(enhancementsResult.state - ? { - enhancements: - enhancementsResult.state as StoredSearchEmbeddableByValueState['enhancements'], - } - : {}), + ...storedState, attributes: { - ...state.attributes, + ...storedState.attributes, ...extractedState.attributes, // discover session stores references as part of attributes references, }, }, - references: [...references, ...enhancementsResult.references], + references: [...references, ...drilldownReferences], }; } return transformIn; diff --git a/src/platform/plugins/shared/discover/common/embeddable/get_transform_out.ts b/src/platform/plugins/shared/discover/common/embeddable/get_transform_out.ts index 653e16f2c67d2..932dea7c1aef0 100644 --- a/src/platform/plugins/shared/discover/common/embeddable/get_transform_out.ts +++ b/src/platform/plugins/shared/discover/common/embeddable/get_transform_out.ts @@ -8,9 +8,10 @@ */ import { extractTabs, SavedSearchType } from '@kbn/saved-search-plugin/common'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { SavedObjectReference } from '@kbn/core/server'; import { transformTitlesOut } from '@kbn/presentation-publishing'; +import { flow } from 'lodash'; import type { SearchEmbeddableByReferenceState, SearchEmbeddableByValueState, @@ -29,19 +30,17 @@ function isByValue( ); } -export function getTransformOut( - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransformOut(transformDrilldownsOut: DrilldownTransforms['transformOut']) { function transformOut( storedState: StoredSearchEmbeddableState, references?: SavedObjectReference[] ) { - const state = transformTitlesOut(storedState); - const enhancementsState = state.enhancements - ? transformEnhancementsOut(state.enhancements, references ?? []) - : undefined; + const transformsFlow = flow( + transformTitlesOut, + (state: StoredSearchEmbeddableState) => transformDrilldownsOut(state, references) + ); + const state = transformsFlow(storedState); - const enhancements = enhancementsState ? { enhancements: enhancementsState } : {}; if (isByValue(state)) { const tabsState = { ...state, @@ -51,7 +50,6 @@ export function getTransformOut( return { ...state, attributes, - ...enhancements, } as SearchEmbeddableByValueState; } @@ -60,7 +58,6 @@ export function getTransformOut( ); return { ...state, - ...enhancements, ...(savedObjectRef?.id ? { savedObjectId: savedObjectRef.id } : {}), } as SearchEmbeddableByReferenceState; } diff --git a/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.test.ts b/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.test.ts index da63f1c6204c7..ca15bb6b3afda 100644 --- a/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.test.ts +++ b/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { createEmbeddableSetupMock } from '@kbn/embeddable-plugin/server/mocks'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { getSearchEmbeddableTransforms } from './search_embeddable_transforms'; import { extract, inject } from './search_inject_extract'; import type { @@ -15,6 +15,7 @@ import type { StoredSearchEmbeddableByValueState, StoredSearchEmbeddableState, SearchEmbeddableByReferenceState, + SearchEmbeddableState, } from './types'; jest.mock('./search_inject_extract', () => { @@ -24,12 +25,13 @@ jest.mock('./search_inject_extract', () => { }; }); -const { transformEnhancementsIn, transformEnhancementsOut } = createEmbeddableSetupMock(); -transformEnhancementsIn.mockImplementation((state) => ({ - state, - references: [], -})); -transformEnhancementsOut.mockImplementation((state) => state); +const mockDrilldownTransforms = { + transformIn: jest.fn().mockImplementation((state: SearchEmbeddableState) => ({ + state, + references: [], + })), + transformOut: jest.fn().mockImplementation((state: StoredSearchEmbeddableState) => state), +} as unknown as DrilldownTransforms; describe('searchEmbeddableTransforms', () => { beforeEach(() => { @@ -41,10 +43,7 @@ describe('searchEmbeddableTransforms', () => { title: 'Test Title', description: 'Test Description', }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformOut?.(state); + const result = getSearchEmbeddableTransforms(mockDrilldownTransforms).transformOut?.(state); expect(result).toEqual(state); }); @@ -79,10 +78,10 @@ describe('searchEmbeddableTransforms', () => { }, ], }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformOut?.(state, references); + const result = getSearchEmbeddableTransforms(mockDrilldownTransforms).transformOut?.( + state, + references + ); expect(inject).toHaveBeenCalledWith( { type: 'search', ...{ ...state, attributes: expectedAttributes } }, references @@ -108,34 +107,24 @@ describe('searchEmbeddableTransforms', () => { }, ], }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformOut?.(state); + const result = getSearchEmbeddableTransforms(mockDrilldownTransforms).transformOut?.(state); expect(result).toEqual({ ...state, attributes: expectedAttributes, }); }); - it('transforms enhancements during transformOut', () => { + it('transforms drilldowns during transformOut', () => { const state: StoredSearchEmbeddableState = { title: 'Test Title', description: 'Test Description', - enhancements: { - dynamicActions: { events: [] }, - }, + drilldowns: [], }; const mockReferences = [{ name: 'enhRef', type: 'dynamicAction', id: 'foo' }]; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformOut?.(state, mockReferences); - expect(transformEnhancementsOut).toHaveBeenCalledWith( - { - dynamicActions: { events: [] }, - }, + const result = getSearchEmbeddableTransforms(mockDrilldownTransforms).transformOut?.( + state, mockReferences ); + expect(mockDrilldownTransforms.transformOut).toHaveBeenCalledWith(state, mockReferences); expect(result).toEqual({ ...state, }); @@ -148,24 +137,20 @@ describe('searchEmbeddableTransforms', () => { savedObjectId: 'test-saved-object-id', title: 'Test Search', description: 'Test Description', - enhancements: { - dynamicActions: { events: [] }, - }, + drilldowns: [], columns: ['field1', 'field2'], sort: [['timestamp', 'desc']], }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformIn!(serializedState); + const result = + getSearchEmbeddableTransforms(mockDrilldownTransforms).transformIn!(serializedState); expect(result.state).toEqual({ title: 'Test Search', description: 'Test Description', columns: ['field1', 'field2'], sort: [['timestamp', 'desc']], - enhancements: expect.any(Object), + drilldowns: [], }); expect(result.references).toEqual([ @@ -176,9 +161,7 @@ describe('searchEmbeddableTransforms', () => { }, ]); - expect(transformEnhancementsIn).toHaveBeenCalledWith({ - dynamicActions: { events: [] }, - }); + expect(mockDrilldownTransforms.transformIn).toHaveBeenCalledWith(serializedState); }); it('handles by-reference state without enhancements', () => { @@ -188,10 +171,8 @@ describe('searchEmbeddableTransforms', () => { columns: ['field1'], }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformIn!(serializedState); + const result = + getSearchEmbeddableTransforms(mockDrilldownTransforms).transformIn!(serializedState); expect(result.state).toEqual({ title: 'Test Search', @@ -205,8 +186,6 @@ describe('searchEmbeddableTransforms', () => { id: 'test-saved-object-id', }, ]); - - expect(transformEnhancementsIn).not.toHaveBeenCalled(); }); }); @@ -230,10 +209,8 @@ describe('searchEmbeddableTransforms', () => { title: 'Panel Title', }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformIn!(serializedState); + const result = + getSearchEmbeddableTransforms(mockDrilldownTransforms).transformIn!(serializedState); expect(extract).toHaveBeenCalledWith({ type: 'search', @@ -241,7 +218,6 @@ describe('searchEmbeddableTransforms', () => { }); expect(result.state as StoredSearchEmbeddableByValueState).toEqual(serializedState); expect(result.references).toEqual([]); - expect(transformEnhancementsIn).not.toHaveBeenCalled(); }); it('handles by-value state with enhancements', () => { @@ -260,20 +236,14 @@ describe('searchEmbeddableTransforms', () => { tabs: [], references: [], }, - enhancements: { - dynamicActions: { events: [] }, - }, + drilldowns: [], }; - const result = getSearchEmbeddableTransforms( - transformEnhancementsIn, - transformEnhancementsOut - ).transformIn!(serializedState); + const result = + getSearchEmbeddableTransforms(mockDrilldownTransforms).transformIn!(serializedState); expect(result.references).toEqual([]); - expect(transformEnhancementsIn).toHaveBeenCalledWith({ - dynamicActions: { events: [] }, - }); + expect(mockDrilldownTransforms.transformIn).toHaveBeenCalledWith(serializedState); expect(extract).toHaveBeenCalledWith({ type: 'search', attributes: serializedState.attributes, diff --git a/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.ts b/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.ts index f693bd3985b32..79b95922eae6b 100644 --- a/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.ts +++ b/src/platform/plugins/shared/discover/common/embeddable/search_embeddable_transforms.ts @@ -7,18 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; -import type { EmbeddableTransforms } from '@kbn/embeddable-plugin/common'; +import type { DrilldownTransforms, EmbeddableTransforms } from '@kbn/embeddable-plugin/common'; import type { SearchEmbeddableState, StoredSearchEmbeddableState } from './types'; import { getTransformIn } from './get_transform_in'; import { getTransformOut } from './get_transform_out'; export function getSearchEmbeddableTransforms( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'], - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] + drilldownTransforms: DrilldownTransforms ): EmbeddableTransforms { return { - transformIn: getTransformIn(transformEnhancementsIn), - transformOut: getTransformOut(transformEnhancementsOut), + transformIn: getTransformIn(drilldownTransforms.transformIn), + transformOut: getTransformOut(drilldownTransforms.transformOut), }; } diff --git a/src/platform/plugins/shared/discover/common/embeddable/types.ts b/src/platform/plugins/shared/discover/common/embeddable/types.ts index 1110f929bd3cd..06788273257f3 100644 --- a/src/platform/plugins/shared/discover/common/embeddable/types.ts +++ b/src/platform/plugins/shared/discover/common/embeddable/types.ts @@ -7,12 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; import type { SerializedTimeRange, SerializedTitles } from '@kbn/presentation-publishing'; import type { SavedSearchAttributes, SavedSearchByValueAttributes, } from '@kbn/saved-search-plugin/common'; +import type { DrilldownsState } from '@kbn/embeddable-plugin/server'; import type { EDITABLE_SAVED_SEARCH_KEYS } from './constants'; // These are options that are not persisted in the saved object, but can be used by solutions @@ -29,7 +29,7 @@ export type EditableSavedSearchAttributes = Partial< type SearchEmbeddableBaseState = SerializedTitles & SerializedTimeRange & - Partial & + DrilldownsState & EditableSavedSearchAttributes & { nonPersistedDisplayOptions?: NonPersistedDisplayOptions; }; diff --git a/src/platform/plugins/shared/discover/public/embeddable/constants.ts b/src/platform/plugins/shared/discover/public/embeddable/constants.ts index bf9b99c40783f..60f9925221073 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/constants.ts +++ b/src/platform/plugins/shared/discover/public/embeddable/constants.ts @@ -34,5 +34,5 @@ export const EDITABLE_PANEL_KEYS: Readonly> = 'description', // panel description 'timeRange', // panel custom time range 'hide_title', // panel hidden title - 'enhancements', // panel enhancements (e.g. drilldowns) + 'drilldowns', // panel drilldowns ] as const; diff --git a/src/platform/plugins/shared/discover/public/embeddable/get_search_embeddable_factory.tsx b/src/platform/plugins/shared/discover/public/embeddable/get_search_embeddable_factory.tsx index 69d1f95e34f5b..f44b422dcb0b2 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/get_search_embeddable_factory.tsx +++ b/src/platform/plugins/shared/discover/public/embeddable/get_search_embeddable_factory.tsx @@ -96,7 +96,7 @@ export const getSearchEmbeddableFactory = ({ const titleManager = initializeTitleManager(initialState); const timeRangeManager = initializeTimeRangeManager(initialState); const dynamicActionsManager = - discoverServices.embeddableEnhanced?.initializeEmbeddableDynamicActions( + await discoverServices.embeddableEnhanced?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -129,7 +129,7 @@ export const getSearchEmbeddableFactory = ({ ), getComparators: () => { return { - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { drilldowns: 'skip', enhancements: 'skip' }), ...titleComparators, ...timeRangeComparators, ...searchEmbeddable.comparators, diff --git a/src/platform/plugins/shared/discover/public/plugin.tsx b/src/platform/plugins/shared/discover/public/plugin.tsx index bb6b06e3e5893..d3bebe9200526 100644 --- a/src/platform/plugins/shared/discover/public/plugin.tsx +++ b/src/platform/plugins/shared/discover/public/plugin.tsx @@ -26,6 +26,7 @@ import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common'; import { i18n } from '@kbn/i18n'; import { once } from 'lodash'; import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common'; import { DISCOVER_CONTEXT_APP_LOCATOR, @@ -455,12 +456,13 @@ export class DiscoverPlugin }); }); - plugins.embeddable.registerLegacyURLTransform(SEARCH_EMBEDDABLE_TYPE, async () => { - const { getSearchEmbeddableTransforms } = await getEmbeddableServices(); - const { transformEnhancementsIn, transformEnhancementsOut } = plugins.embeddable; - return getSearchEmbeddableTransforms(transformEnhancementsIn, transformEnhancementsOut) - .transformOut; - }); + plugins.embeddable.registerLegacyURLTransform( + SEARCH_EMBEDDABLE_TYPE, + async (transformDrilldownsOut: DrilldownTransforms['transformOut']) => { + const { getTransformOut } = await getEmbeddableServices(); + return getTransformOut(transformDrilldownsOut); + } + ); } } diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts b/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts index 0e3199e93b550..2f261daaae701 100644 --- a/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts @@ -11,4 +11,4 @@ export { ViewSavedSearchAction } from '../embeddable/actions/view_saved_search_a export { addPanelFromLibrary } from '../embeddable/utils/add_panel_from_library'; export { getSearchEmbeddableFactory } from '../embeddable/get_search_embeddable_factory'; export { getLegacyLogStreamEmbeddableFactory } from '../embeddable/get_legacy_log_stream_embeddable_factory'; -export { getSearchEmbeddableTransforms } from '../../common/embeddable'; +export { getTransformOut } from '../../common/embeddable/get_transform_out'; diff --git a/src/platform/plugins/shared/discover/server/plugin.ts b/src/platform/plugins/shared/discover/server/plugin.ts index 277ffd2977080..67767aa515222 100644 --- a/src/platform/plugins/shared/discover/server/plugin.ts +++ b/src/platform/plugins/shared/discover/server/plugin.ts @@ -65,13 +65,9 @@ export class DiscoverServerPlugin } plugins.embeddable.registerEmbeddableFactory(createSearchEmbeddableFactory()); - plugins.embeddable.registerTransforms( - SEARCH_EMBEDDABLE_TYPE, - getSearchEmbeddableTransforms( - plugins.embeddable.transformEnhancementsIn, - plugins.embeddable.transformEnhancementsOut - ) - ); + plugins.embeddable.registerTransforms(SEARCH_EMBEDDABLE_TYPE, { + getTransforms: getSearchEmbeddableTransforms, + }); core.pricing.registerProductFeatures([ { diff --git a/src/platform/plugins/shared/embeddable/README.md b/src/platform/plugins/shared/embeddable/README.md index 2a5f3619b772d..7dc89e5ab8299 100644 --- a/src/platform/plugins/shared/embeddable/README.md +++ b/src/platform/plugins/shared/embeddable/README.md @@ -212,16 +212,18 @@ Containers use schemas to embeddableServerSetup.registerTransforms( 'myEmbeddableType', { - transformIn: (state: EmbeddableState) => { - return { - state: convertToStoredState(state), - references: extractReferences(state) - }; - }, - transformOut: (state: StoredEmbeddableState, references?: Reference[]): EmbeddableState => { - return convertAndInjectReferences(state, references); - }, - schema: schema.object({ + getTransforms: (drilldownTransfroms) => ({ + transformIn: (state: EmbeddableState) => { + return { + state: convertToStoredState(state), + references: extractReferences(state) + }; + }, + transformOut: (state: StoredEmbeddableState, references?: Reference[]): EmbeddableState => { + return convertAndInjectReferences(state, references); + }, + }), + getSchema: (getDrilldownsSchema) => schema: schema.object({ required_field: schema.string(), optional_field: schema.maybe(schema.string()), }) diff --git a/src/platform/plugins/shared/embeddable/common/bwc/enhancements/dynamic_actions/dashboard_drilldown_persistable_state.ts b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/dynamic_actions/dashboard_drilldown_persistable_state.ts index 18e3c0f7ae634..1b6dd291ee189 100644 --- a/src/platform/plugins/shared/embeddable/common/bwc/enhancements/dynamic_actions/dashboard_drilldown_persistable_state.ts +++ b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/dynamic_actions/dashboard_drilldown_persistable_state.ts @@ -12,7 +12,7 @@ import type { SerializedAction, SerializedEvent } from './types'; export const EMBEDDABLE_TO_DASHBOARD_DRILLDOWN = 'DASHBOARD_TO_DASHBOARD_DRILLDOWN'; -const generateRefName = (eventId: string) => +export const generateRefName = (eventId: string) => `drilldown:${EMBEDDABLE_TO_DASHBOARD_DRILLDOWN}:${eventId}:dashboardId`; export const dashboardDrilldownPersistableState = { diff --git a/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.test.ts b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.test.ts new file mode 100644 index 0000000000000..ec05aeca600fc --- /dev/null +++ b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.test.ts @@ -0,0 +1,154 @@ +/* + * 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 { transformEnhancementsOut } from './transform_enhancements_out'; + +describe('transformEnhancementsOut', () => { + test('should convert dashboard drilldown event', () => { + const state = { + enhancements: { + dynamicActions: { + events: [ + { + action: { + config: { + openInNewTab: false, + useCurrentDateRange: true, + useCurrentFilters: true, + }, + factoryId: 'DASHBOARD_TO_DASHBOARD_DRILLDOWN', + name: 'Go to Dashboard', + }, + eventId: '8aeddba7-a7ed-42e2-988e-794c8435028d', + triggers: ['FILTER_TRIGGER'], + }, + ], + }, + }, + }; + expect(transformEnhancementsOut(state)).toMatchInlineSnapshot(` + Object { + "drilldowns": Array [ + Object { + "dashboardRefName": "drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:8aeddba7-a7ed-42e2-988e-794c8435028d:dashboardId", + "label": "Go to Dashboard", + "open_in_new_tab": false, + "trigger": "FILTER_TRIGGER", + "type": "dashboard_drilldown", + "use_filters": true, + "use_time_range": true, + }, + ], + } + `); + }); + + test('should convert discover drilldown event', () => { + const state = { + enhancements: { + dynamicActions: { + events: [ + { + action: { + config: { + openInNewTab: false, + }, + factoryId: 'OPEN_IN_DISCOVER_DRILLDOWN', + name: 'Open in Discover', + }, + eventId: '8b3b25b4-1691-4826-82c4-fb6c0478f669', + triggers: ['FILTER_TRIGGER'], + }, + ], + }, + }, + }; + expect(transformEnhancementsOut(state)).toMatchInlineSnapshot(` + Object { + "drilldowns": Array [ + Object { + "label": "Open in Discover", + "open_in_new_tab": false, + "trigger": "FILTER_TRIGGER", + "type": "discover_drilldown", + }, + ], + } + `); + }); + + test('should convert url drilldown event', () => { + const state = { + enhancements: { + dynamicActions: { + events: [ + { + action: { + config: { + encodeUrl: true, + openInNewTab: true, + url: { + template: 'https://localhost/?{{event.key}}', + }, + }, + factoryId: 'URL_DRILLDOWN', + name: 'Go to URL', + }, + eventId: 'c29b72e0-8a32-4214-abe0-6c54c6f804b7', + triggers: ['VALUE_CLICK_TRIGGER'], + }, + ], + }, + }, + }; + expect(transformEnhancementsOut(state)).toMatchInlineSnapshot(` + Object { + "drilldowns": Array [ + Object { + "encode_url": true, + "label": "Go to URL", + "open_in_new_tab": true, + "trigger": "VALUE_CLICK_TRIGGER", + "type": "url_drilldown", + "url": "https://localhost/?{{event.key}}", + }, + ], + } + `); + }); + + test('should discard unknown events and preserve all other state keys', () => { + const state = { + enhancements: { + dynamicActions: { + events: [ + { + action: { + config: { + foo: 'hello', + }, + factoryId: 'UNKNOWN_FACTORY', + name: 'I am an unknown event', + }, + eventId: '8b3b25b4-1691-4826-82c4-fb6c0478f669', + triggers: ['FILTER_TRIGGER'], + }, + ], + }, + }, + // Should not remove other state keys + someOtherKey: 'foo', + }; + expect(transformEnhancementsOut(state)).toMatchInlineSnapshot(` + Object { + "someOtherKey": "foo", + } + `); + }); +}); diff --git a/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.ts b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.ts index e77eefeea3a0b..4ea2bd52456bc 100644 --- a/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.ts +++ b/src/platform/plugins/shared/embeddable/common/bwc/enhancements/transform_enhancements_out.ts @@ -7,46 +7,84 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { Reference } from '@kbn/content-management-utils'; -import type { DynamicActionsState } from './dynamic_actions/types'; -import { enhancementsPersistableState } from './enhancements_persistable_state'; - -export function transformEnhancementsOut( - enhancementsState: { dynamicActions?: DynamicActionsState }, - references: Reference[] -) { - if (!enhancementsState?.dynamicActions?.events) return {}; - - const events = enhancementsState.dynamicActions.events.map((event) => { - if (event.action.factoryId !== 'DASHBOARD_TO_DASHBOARD_DRILLDOWN') { - return event; - } - - const { openInNewTab, useCurrentDateRange, useCurrentFilters, ...restOfConfig } = - event.action.config; - - return { - ...event, - action: { - ...event.action, - config: { - ...restOfConfig, - ...(typeof openInNewTab === 'boolean' ? { open_in_new_tab: openInNewTab } : {}), - ...(typeof useCurrentDateRange === 'boolean' - ? { use_time_range: useCurrentDateRange } - : {}), - ...(typeof useCurrentFilters === 'boolean' ? { use_filters: useCurrentFilters } : {}), - }, - }, - }; - }); - - return enhancementsPersistableState.inject( - { - dynamicActions: { - events, - }, - }, - references - ); +import type { DrilldownsState } from '../../../server'; +import { generateRefName } from './dynamic_actions/dashboard_drilldown_persistable_state'; +import type { DynamicActionsState, SerializedEvent } from './dynamic_actions/types'; + +export function transformEnhancementsOut( + state: StoredState & { enhancements?: { dynamicActions?: DynamicActionsState } } +): StoredState { + const { enhancements, ...restOfState } = state; + + if ( + !enhancements?.dynamicActions?.events || + !Array(enhancements.dynamicActions.events) || + !enhancements.dynamicActions.events.length + ) { + return restOfState as StoredState; + } + + const drilldownsFromEnhancements = enhancements.dynamicActions.events + .map((event) => { + if (event.action.factoryId === 'DASHBOARD_TO_DASHBOARD_DRILLDOWN') { + return convertToDashboardDrilldown(event); + } + + if (event.action.factoryId === 'OPEN_IN_DISCOVER_DRILLDOWN') { + return convertToDiscoverDrilldown(event); + } + + if (event.action.factoryId === 'URL_DRILLDOWN') { + return convertToUrlDrilldown(event); + } + }) + .filter((drilldown) => drilldown !== undefined); + + return drilldownsFromEnhancements.length + ? { + ...(restOfState as StoredState), + drilldowns: [...drilldownsFromEnhancements, ...(restOfState.drilldowns ?? [])], + } + : (restOfState as StoredState); +} + +function convertToDashboardDrilldown(event: SerializedEvent) { + const { openInNewTab, useCurrentDateRange, useCurrentFilters } = event.action.config; + + return { + dashboardRefName: generateRefName(event.eventId), + label: event.action.name, + open_in_new_tab: openInNewTab ?? false, + trigger: event.triggers[0] ?? 'unknown', + type: 'dashboard_drilldown', + use_filters: useCurrentFilters ?? true, + use_time_range: useCurrentDateRange ?? true, + }; +} + +function convertToDiscoverDrilldown(event: SerializedEvent) { + const { openInNewTab } = event.action.config; + + return { + label: event.action.name, + open_in_new_tab: openInNewTab ?? false, + trigger: event.triggers[0] ?? 'unknown', + type: 'discover_drilldown', + }; +} + +function convertToUrlDrilldown(event: SerializedEvent) { + const { encodeUrl, openInNewTab, url } = event.action.config as { + encodeUrl?: boolean; + openInNewTab?: boolean; + url?: { template?: string }; + }; + return { + label: event.action.name, + encode_url: encodeUrl ?? true, + open_in_new_tab: openInNewTab ?? true, + trigger: event.triggers[0] ?? 'unknown', + type: 'url_drilldown', + url: url?.template ?? '', + }; } diff --git a/src/platform/plugins/shared/embeddable/common/constants.ts b/src/platform/plugins/shared/embeddable/common/constants.ts new file mode 100644 index 0000000000000..c76e0cd00fdb9 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/common/constants.ts @@ -0,0 +1,12 @@ +/* + * 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". + */ + +export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; +export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; +export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; diff --git a/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_in.ts b/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_in.ts new file mode 100644 index 0000000000000..f50b81289f0e7 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_in.ts @@ -0,0 +1,52 @@ +/* + * 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 { Reference } from '@kbn/content-management-utils'; +import type { DrilldownsState, DrilldownState } from '../../server'; + +export function getTransformDrilldownsIn( + getTranformIn: (type: string) => + | ((state: DrilldownState) => { + state: DrilldownState; + references?: Reference[]; + }) + | undefined +) { + function transformDrilldownsIn(state: State) { + const { drilldowns, ...restOfState } = state; + if (!drilldowns) { + return { + state: state as State, + references: [], + }; + } + + const references: Reference[] = []; + return { + state: { + ...(restOfState as State), + drilldowns: drilldowns.map((drilldownState) => { + const transformIn = getTranformIn(drilldownState.type); + if (!transformIn) { + return drilldownState; + } + + const { state: storedDrilldownState, references: drilldownReferences } = + transformIn(drilldownState); + if (drilldownReferences) { + references.push(...drilldownReferences); + } + return storedDrilldownState; + }), + }, + references, + }; + } + return transformDrilldownsIn; +} diff --git a/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_out.ts b/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_out.ts new file mode 100644 index 0000000000000..9f59a7e081ccc --- /dev/null +++ b/src/platform/plugins/shared/embeddable/common/drilldowns/transform_drilldowns_out.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", 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 { Reference } from '@kbn/content-management-utils'; +import type { DrilldownsState, DrilldownState } from '../../server'; +import { transformEnhancementsOut } from '../bwc/enhancements/transform_enhancements_out'; + +export function getTransformDrilldownsOut( + getTranformOut: ( + type: string + ) => ((state: DrilldownState, references?: Reference[]) => DrilldownState) | undefined +) { + function transformDrilldownsOut( + storedState: StoredState, + references?: Reference[] + ): StoredState { + const { drilldowns, ...restOfState } = transformEnhancementsOut(storedState); + return drilldowns?.length + ? { + ...(restOfState as StoredState), + drilldowns: drilldowns.map((drilldownState) => { + const transformOut = getTranformOut(drilldownState.type); + return transformOut ? transformOut(drilldownState, references) : drilldownState; + }), + } + : (restOfState as StoredState); + } + return transformDrilldownsOut; +} diff --git a/src/platform/plugins/shared/embeddable/common/drilldowns/types.ts b/src/platform/plugins/shared/embeddable/common/drilldowns/types.ts new file mode 100644 index 0000000000000..3245c413ed2ca --- /dev/null +++ b/src/platform/plugins/shared/embeddable/common/drilldowns/types.ts @@ -0,0 +1,16 @@ +/* + * 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 { getTransformDrilldownsIn } from './transform_drilldowns_in'; +import type { getTransformDrilldownsOut } from './transform_drilldowns_out'; + +export type DrilldownTransforms = { + transformIn: ReturnType; + transformOut: ReturnType; +}; diff --git a/src/platform/plugins/shared/embeddable/common/index.ts b/src/platform/plugins/shared/embeddable/common/index.ts index 8db5010075b23..680404110b4dd 100644 --- a/src/platform/plugins/shared/embeddable/common/index.ts +++ b/src/platform/plugins/shared/embeddable/common/index.ts @@ -7,10 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { Reference } from '@kbn/content-management-utils'; -import type { SerializableRecord } from '@kbn/utility-types'; +export type { DrilldownTransforms } from './drilldowns/types'; -export type { EmbeddableTransforms } from './types'; +export type { EmbeddableTransforms } from '../server'; export type { EmbeddableRegistryDefinition, @@ -18,12 +17,4 @@ export type { EmbeddablePersistableStateService, } from '../server'; -export type TransformEnhancementsIn = (enhancementsState: SerializableRecord) => { - state: SerializableRecord; - references: Reference[]; -}; - -export type TransformEnhancementsOut = ( - enhancementsState: SerializableRecord, - references: Reference[] -) => SerializableRecord; +export { CONTEXT_MENU_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from './constants'; diff --git a/src/platform/plugins/shared/embeddable/public/async_module.ts b/src/platform/plugins/shared/embeddable/public/async_module.ts new file mode 100644 index 0000000000000..fd0c552a837c0 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/public/async_module.ts @@ -0,0 +1,11 @@ +/* + * 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". + */ + +export { getTransformDrilldownsOut } from '../common/drilldowns/transform_drilldowns_out'; +export { transformDashboardDrilldown } from './bwc/dashboard_drilldown'; diff --git a/src/platform/plugins/shared/embeddable/public/bwc/dashboard_drilldown.ts b/src/platform/plugins/shared/embeddable/public/bwc/dashboard_drilldown.ts new file mode 100644 index 0000000000000..fa9e248d29b72 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/public/bwc/dashboard_drilldown.ts @@ -0,0 +1,27 @@ +/* + * 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 { Reference } from '@kbn/content-management-utils/src/types'; +import type { DrilldownState } from '../../server'; + +export function transformDashboardDrilldown( + urlState: DrilldownState & { dashboardRefName?: string }, + references?: Reference[] +) { + if (!urlState.dashboardRefName) { + return urlState; + } + + const { dashboardRefName, ...rest } = urlState; + const reference = references?.find(({ name }) => name === dashboardRefName); + return { + ...rest, + dashboard_id: reference?.id ?? '', + }; +} diff --git a/src/platform/plugins/shared/embeddable/public/bwc/legacy_url_transform.ts b/src/platform/plugins/shared/embeddable/public/bwc/legacy_url_transform.ts new file mode 100644 index 0000000000000..de270b0a4cb39 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/public/bwc/legacy_url_transform.ts @@ -0,0 +1,48 @@ +/* + * 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 { DrilldownTransforms, EmbeddableTransforms } from '../../common'; +import type { getTransformDrilldownsOut } from '../../common/drilldowns/transform_drilldowns_out'; + +const registry: { + [key: string]: ( + transformDrilldownsOut: ReturnType + ) => Promise; +} = {}; + +/* + * Container applications such as Dashboard store embeddable state in URLs. + * Use this registry for BWC when reading state from URLs + */ +export function registerLegacyURLTransform( + type: string, + getTransformOut: ( + transformDrilldownsOut: DrilldownTransforms['transformOut'] + ) => Promise +) { + if (registry[type]) { + throw new Error(`Embeddable legacy URL transform for type "${type}" is already registered.`); + } + + registry[type] = getTransformOut; +} + +export async function getLegacyURLTransform(embeddableType: string) { + const { getTransformDrilldownsOut, transformDashboardDrilldown } = await import( + '../async_module' + ); + const transformDrilldownsOut = getTransformDrilldownsOut((drilldownType: string) => { + return drilldownType === 'dashboard_drilldown' ? transformDashboardDrilldown : undefined; + }); + return await registry[embeddableType]?.(transformDrilldownsOut); +} + +export function hasLegacyURLTransform(type: string) { + return Boolean(registry[type]); +} diff --git a/src/platform/plugins/shared/embeddable/public/index.ts b/src/platform/plugins/shared/embeddable/public/index.ts index d9503080a4b75..c06469c7c5cf9 100644 --- a/src/platform/plugins/shared/embeddable/public/index.ts +++ b/src/platform/plugins/shared/embeddable/public/index.ts @@ -17,7 +17,6 @@ export { cellValueTrigger, CELL_VALUE_TRIGGER, contextMenuTrigger, - CONTEXT_MENU_TRIGGER, isMultiValueClickTriggerContext, isRangeSelectTriggerContext, isRowClickTriggerContext, @@ -27,9 +26,8 @@ export { panelNotificationTrigger, PANEL_BADGE_TRIGGER, PANEL_NOTIFICATION_TRIGGER, - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, } from './ui_actions/triggers'; +export { CONTEXT_MENU_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../common'; export type { CellValueContext, ChartActionContext, diff --git a/src/platform/plugins/shared/embeddable/public/mocks.tsx b/src/platform/plugins/shared/embeddable/public/mocks.tsx index 7034126179db7..b1216e2bb4824 100644 --- a/src/platform/plugins/shared/embeddable/public/mocks.tsx +++ b/src/platform/plugins/shared/embeddable/public/mocks.tsx @@ -49,8 +49,6 @@ const createSetupContract = (): Setup => { registerAddFromLibraryType: jest.fn().mockImplementation(registerAddFromLibraryType), registerReactEmbeddableFactory: jest.fn().mockImplementation(registerReactEmbeddableFactory), registerLegacyURLTransform: jest.fn(), - transformEnhancementsIn: jest.fn(), - transformEnhancementsOut: jest.fn(), }; return setupContract; }; diff --git a/src/platform/plugins/shared/embeddable/public/plugin.tsx b/src/platform/plugins/shared/embeddable/public/plugin.tsx index 23419e17f4f91..f6a46d2cbc578 100644 --- a/src/platform/plugins/shared/embeddable/public/plugin.tsx +++ b/src/platform/plugins/shared/embeddable/public/plugin.tsx @@ -31,9 +31,7 @@ import { registerLegacyURLTransform, hasLegacyURLTransform, getLegacyURLTransform, -} from './transforms_registry'; -import { enhancementsPersistableState } from '../common/bwc/enhancements/enhancements_persistable_state'; -import { transformEnhancementsOut } from '../common/bwc/enhancements/transform_enhancements_out'; +} from './bwc/legacy_url_transform'; export class EmbeddablePublicPlugin implements Plugin { private stateTransferService: EmbeddableStateTransfer = {} as EmbeddableStateTransfer; @@ -49,8 +47,6 @@ export class EmbeddablePublicPlugin implements Plugin Promise; -} = {}; - -export function registerLegacyURLTransform( - type: string, - getTransformOut: () => Promise -) { - if (registry[type]) { - throw new Error(`Embeddable legacy URL transform for type "${type}" is already registered.`); - } - - registry[type] = getTransformOut; -} - -export async function getLegacyURLTransform(type: string) { - return await registry[type]?.(); -} - -export function hasLegacyURLTransform(type: string) { - return Boolean(registry[type]); -} diff --git a/src/platform/plugins/shared/embeddable/public/types.ts b/src/platform/plugins/shared/embeddable/public/types.ts index ada9769acc4ce..45417d44818c8 100644 --- a/src/platform/plugins/shared/embeddable/public/types.ts +++ b/src/platform/plugins/shared/embeddable/public/types.ts @@ -17,11 +17,7 @@ import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { registerAddFromLibraryType } from './add_from_library/registry'; import type { registerReactEmbeddableFactory } from './react_embeddable_system'; import type { EmbeddableStateTransfer } from './state_transfer'; -import type { - EmbeddableTransforms, - TransformEnhancementsIn, - TransformEnhancementsOut, -} from '../common'; +import type { DrilldownTransforms, EmbeddableTransforms } from '../common'; import type { AddFromLibraryFormProps } from './add_from_library/add_from_library_flyout'; export interface EmbeddableSetupDependencies { @@ -77,11 +73,10 @@ export interface EmbeddableSetup { */ registerLegacyURLTransform: ( type: string, - getTransformOut: () => Promise + getTransformOut: ( + transformDrilldownsOut: DrilldownTransforms['transformOut'] + ) => Promise ) => void; - - transformEnhancementsIn: TransformEnhancementsIn; - transformEnhancementsOut: TransformEnhancementsOut; } export interface EmbeddableStart { diff --git a/src/platform/plugins/shared/embeddable/public/ui_actions/triggers.ts b/src/platform/plugins/shared/embeddable/public/ui_actions/triggers.ts index 099ed0cb2ba79..41177828612d2 100644 --- a/src/platform/plugins/shared/embeddable/public/ui_actions/triggers.ts +++ b/src/platform/plugins/shared/embeddable/public/ui_actions/triggers.ts @@ -12,6 +12,7 @@ import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; import type { Datatable, DatatableColumnMeta } from '@kbn/expressions-plugin/common'; import type { Trigger, RowClickContext } from '@kbn/ui-actions-plugin/public'; import type { BooleanRelation } from '@kbn/es-query'; +import { CONTEXT_MENU_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../../common'; export type ValueClickContext = Partial & { data: { @@ -64,7 +65,6 @@ export type ChartActionContext = | RangeSelectContext | RowClickContext; -export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger = { id: CONTEXT_MENU_TRIGGER, title: i18n.translate('embeddableApi.contextMenuTrigger.title', { @@ -97,7 +97,6 @@ export const panelNotificationTrigger: Trigger = { }), }; -export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; export const selectRangeTrigger: Trigger = { id: SELECT_RANGE_TRIGGER, title: i18n.translate('embeddableApi.selectRangeTrigger.title', { @@ -108,7 +107,6 @@ export const selectRangeTrigger: Trigger = { }), }; -export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; export const valueClickTrigger: Trigger = { id: VALUE_CLICK_TRIGGER, title: i18n.translate('embeddableApi.valueClickTrigger.title', { diff --git a/src/platform/plugins/shared/embeddable/server/drilldowns/registry.test.ts b/src/platform/plugins/shared/embeddable/server/drilldowns/registry.test.ts new file mode 100644 index 0000000000000..72875e0e5c25d --- /dev/null +++ b/src/platform/plugins/shared/embeddable/server/drilldowns/registry.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { getDrilldownRegistry } from './registry'; + +describe('drilldown registry', () => { + describe('getSchemas', () => { + const registry = getDrilldownRegistry(); + registry.registerDrilldown('foo_drilldown', { + schema: schema.object({ + foo: schema.string(), + }), + supportedTriggers: ['ON_CLICK', 'ON_HOVER', 'ON_ROW_CLICK'], + }); + registry.registerDrilldown('bar_drilldown', { + schema: schema.object({ + bar: schema.string(), + }), + supportedTriggers: ['ON_CLICK'], + }); + + function getType(drilldownJoiSchema: any) { + return drilldownJoiSchema.schema.keys.type.allow[0]; + } + + function getTriggerLiterals(drilldownJoiSchema: any) { + return drilldownJoiSchema.schema.keys.trigger.matches.map( + (match: any) => match.schema.allow[0] + ); + } + + test('should include drilldowns that intersect with supported triggers', () => { + const onClickDrilldownsSchema = registry.getSchema(['ON_CLICK']); + const drilldownsJoiSchema = onClickDrilldownsSchema.getPropSchemas().drilldowns?.getSchema(); + const matches = drilldownsJoiSchema.describe().items[0].matches; + expect(matches.length).toBe(2); + expect(getType(matches[0])).toBe('foo_drilldown'); + expect(getType(matches[1])).toBe('bar_drilldown'); + }); + + test('should remove drilldowns that do not intersect with supported triggers', () => { + const onHoverDrilldownsSchema = registry.getSchema(['ON_HOVER']); + const drilldownsJoiSchema = onHoverDrilldownsSchema.getPropSchemas().drilldowns?.getSchema(); + const matches = drilldownsJoiSchema.describe().items[0].matches; + expect(matches.length).toBe(1); + expect(getType(matches[0])).toBe('foo_drilldown'); + }); + + test('should include triggers that intersect with supported triggers', () => { + const drilldownsSchema = registry.getSchema(['ON_CLICK', 'ON_ROW_CLICK']); + const drilldownsJoiSchema = drilldownsSchema.getPropSchemas().drilldowns?.getSchema(); + const matches = drilldownsJoiSchema.describe().items[0].matches; + // foo drilldown schema should not show 'ON_HOVER' because that trigger does not intersect supported triggers + expect(getTriggerLiterals(matches[0])).toEqual(['ON_CLICK', 'ON_ROW_CLICK']); + expect(getTriggerLiterals(matches[1])).toEqual(['ON_CLICK']); + }); + }); +}); diff --git a/src/platform/plugins/shared/embeddable/server/drilldowns/registry.ts b/src/platform/plugins/shared/embeddable/server/drilldowns/registry.ts new file mode 100644 index 0000000000000..3b43d2b6d8cd9 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/server/drilldowns/registry.ts @@ -0,0 +1,77 @@ +/* + * 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 { Type } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import type { DrilldownSetup } from './types'; +import { getTransformDrilldownsIn } from '../../common/drilldowns/transform_drilldowns_in'; +import { getTransformDrilldownsOut } from '../../common/drilldowns/transform_drilldowns_out'; + +export function getDrilldownRegistry() { + const registry: { [key: string]: DrilldownSetup } = {}; + + function getTransformIn(type: string) { + return registry[type]?.transformIn; + } + + function getTransformOut(type: string) { + return registry[type]?.transformOut; + } + + return { + registerDrilldown: (type: string, drilldown: DrilldownSetup) => { + if (registry[type]) { + throw new Error(`Drilldown for type "${type}" are already registered.`); + } + + registry[type] = drilldown; + }, + transforms: { + transformIn: getTransformDrilldownsIn(getTransformIn), + transformOut: getTransformDrilldownsOut(getTransformOut), + }, + getSchema: (supportedTriggers: string[]) => { + const drilldownSchemas = Object.entries(registry) + // narrow drilldowns to only those that intersect with supported triggers + .filter(([type, drilldownSetup]) => { + return supportedTriggers.some((trigger) => + drilldownSetup.supportedTriggers.includes(trigger) + ); + }) + .map(([type, drilldownSetup]) => + drilldownSetup.schema.extends({ + label: schema.string(), + trigger: schema.oneOf( + drilldownSetup.supportedTriggers + // narrow drilldown triggers to only those that intersect with supported triggers + .filter((trigger) => supportedTriggers.includes(trigger)) + .map((trigger) => schema.literal(trigger)) as [Type] + ), + type: schema.literal(type), + }) + ); + + return schema.object({ + drilldowns: schema.maybe( + schema.arrayOf( + schema.oneOf( + drilldownSchemas as unknown as [ + Type> + ] + ), + { + // 100 is an arbitrary limit + maxSize: 100, + } + ) + ), + }); + }, + }; +} diff --git a/src/platform/plugins/shared/embeddable/server/drilldowns/types.ts b/src/platform/plugins/shared/embeddable/server/drilldowns/types.ts new file mode 100644 index 0000000000000..af1d569e0413d --- /dev/null +++ b/src/platform/plugins/shared/embeddable/server/drilldowns/types.ts @@ -0,0 +1,49 @@ +/* + * 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 { ObjectType, Type } from '@kbn/config-schema'; +import type { Reference } from '@kbn/content-management-utils'; + +export type DrilldownState = { label: string; trigger: string; type: string }; + +export type DrilldownsState = { + drilldowns?: DrilldownState[]; +}; + +export type DrilldownSetup< + StoredState extends DrilldownState = DrilldownState, + State extends DrilldownState = DrilldownState +> = { + /** + * Schema defining distinct state for the drilldown type + */ + schema: ObjectType; + /** + * List of triggers supported by this drilldown type + * Used to + * 1) narrow registry schemas by intersection of (embeddable) supported triggers + * 2) populate triggers schema + */ + supportedTriggers: string[]; + /** + * Called on REST read routes to inject references and convert Stored State into API State + */ + transformOut?: (storedState: StoredState, references?: Reference[]) => State; + /** + * Called on REST write routes to convert API State into Stored State and extracts references + */ + transformIn?: (state: State) => { + state: StoredState; + references?: Reference[]; + }; +}; + +export type GetDrilldownsSchemaFnType = (embeddableSupportedTriggers: string[]) => ObjectType<{ + drilldowns: Type; +}>; diff --git a/src/platform/plugins/shared/embeddable/server/embeddable_transforms/registry.ts b/src/platform/plugins/shared/embeddable/server/embeddable_transforms/registry.ts new file mode 100644 index 0000000000000..302d36a68edd1 --- /dev/null +++ b/src/platform/plugins/shared/embeddable/server/embeddable_transforms/registry.ts @@ -0,0 +1,38 @@ +/* + * 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 { ObjectType } from '@kbn/config-schema'; +import type { getDrilldownRegistry } from '../drilldowns/registry'; +import type { EmbeddableTransformsSetup } from './types'; + +export function getTransformsRegistry(drilldownRegistry: ReturnType) { + const transformsRegistry: { [key: string]: EmbeddableTransformsSetup } = {}; + + return { + registerTransforms: (type: string, transforms: EmbeddableTransformsSetup) => { + if (transformsRegistry[type]) { + throw new Error(`Embeddable transforms for type "${type}" are already registered.`); + } + + transformsRegistry[type] = transforms; + }, + getAllEmbeddableSchemas: () => + Object.values(transformsRegistry) + .map((transformsSetup) => transformsSetup?.getSchema?.(drilldownRegistry.getSchema)) + .filter((schema) => Boolean(schema)) as ObjectType[], + getEmbeddableTransforms: (type: string) => { + const { getTransforms, getSchema, throwOnUnmappedPanel } = transformsRegistry[type] ?? {}; + return { + ...getTransforms?.(drilldownRegistry.transforms), + ...(getSchema ? { schema: getSchema(drilldownRegistry.getSchema) } : {}), + ...(typeof throwOnUnmappedPanel === 'boolean' ? { throwOnUnmappedPanel } : {}), + }; + }, + }; +} diff --git a/src/platform/plugins/shared/embeddable/common/types.ts b/src/platform/plugins/shared/embeddable/server/embeddable_transforms/types.ts similarity index 81% rename from src/platform/plugins/shared/embeddable/common/types.ts rename to src/platform/plugins/shared/embeddable/server/embeddable_transforms/types.ts index 624eddc1ebf27..f28beb0bc78ec 100644 --- a/src/platform/plugins/shared/embeddable/common/types.ts +++ b/src/platform/plugins/shared/embeddable/server/embeddable_transforms/types.ts @@ -9,6 +9,8 @@ import type { Type } from '@kbn/config-schema'; import type { Reference } from '@kbn/content-management-utils'; +import type { DrilldownTransforms } from '../../common'; +import type { GetDrilldownsSchemaFnType } from '../drilldowns/types'; export type EmbeddableTransforms< StoredEmbeddableState extends object = object, @@ -39,6 +41,15 @@ export type EmbeddableTransforms< state: StoredEmbeddableState; references?: Reference[]; }; +}; + +export type EmbeddableTransformsSetup< + StoredEmbeddableState extends object = object, + EmbeddableState extends object = object +> = { + getTransforms?: ( + drilldownTransforms: DrilldownTransforms + ) => EmbeddableTransforms; /** * Embeddable containers that include embeddable state in REST APIs, such as dashboard, * use schemas to @@ -47,7 +58,7 @@ export type EmbeddableTransforms< * * When schema is provided, EmbeddableState is expected to be TypeOf */ - getSchema?: () => Type | undefined; + getSchema?: (getDrilldownsSchema: GetDrilldownsSchemaFnType) => Type | undefined; /** * Throws error when panel config is not supported. */ diff --git a/src/platform/plugins/shared/embeddable/server/index.ts b/src/platform/plugins/shared/embeddable/server/index.ts index aa770edaf2ded..274cf22e515a8 100644 --- a/src/platform/plugins/shared/embeddable/server/index.ts +++ b/src/platform/plugins/shared/embeddable/server/index.ts @@ -13,6 +13,14 @@ export type { EmbeddableSetup, EmbeddableStart }; export type { EmbeddableRegistryDefinition } from './types'; +export type { + DrilldownState, + DrilldownsState, + GetDrilldownsSchemaFnType, +} from './drilldowns/types'; + +export type { EmbeddableTransforms } from './embeddable_transforms/types'; + export type { EmbeddableStateWithType, EmbeddablePersistableStateService, diff --git a/src/platform/plugins/shared/embeddable/server/mocks.ts b/src/platform/plugins/shared/embeddable/server/mocks.ts index 5c28c4678bd72..84b69f6888ffc 100644 --- a/src/platform/plugins/shared/embeddable/server/mocks.ts +++ b/src/platform/plugins/shared/embeddable/server/mocks.ts @@ -12,15 +12,14 @@ import type { EmbeddableSetup, EmbeddableStart } from './plugin'; export const createEmbeddableSetupMock = (): jest.Mocked => ({ ...createEmbeddablePersistableStateServiceMock(), + registerDrilldown: jest.fn(), registerEmbeddableFactory: jest.fn(), registerTransforms: jest.fn(), getAllMigrations: jest.fn().mockReturnValue({}), - transformEnhancementsIn: jest.fn(), - transformEnhancementsOut: jest.fn(), }); export const createEmbeddableStartMock = (): jest.Mocked => ({ ...createEmbeddablePersistableStateServiceMock(), - getEmbeddableSchemas: jest.fn(), + getAllEmbeddableSchemas: jest.fn(), getTransforms: jest.fn(), }); diff --git a/src/platform/plugins/shared/embeddable/server/plugin.ts b/src/platform/plugins/shared/embeddable/server/plugin.ts index b2c4d2d20c9d7..b65894ecb0428 100644 --- a/src/platform/plugins/shared/embeddable/server/plugin.ts +++ b/src/platform/plugins/shared/embeddable/server/plugin.ts @@ -15,7 +15,7 @@ import type { MigrateFunctionsObject, PersistableState, } from '@kbn/kibana-utils-plugin/common'; -import type { ObjectType } from '@kbn/config-schema'; +import type { ObjectType, Type } from '@kbn/config-schema'; import type { EmbeddableFactoryRegistry, EmbeddableRegistryDefinition } from './types'; import type { EmbeddableStateWithType } from './persistable_state/types'; import { @@ -25,16 +25,24 @@ import { getTelemetryFunction, } from './persistable_state'; import { getAllMigrations } from './persistable_state/get_all_migrations'; -import type { - EmbeddableTransforms, - TransformEnhancementsIn, - TransformEnhancementsOut, -} from '../common'; -import { enhancementsPersistableState } from '../common/bwc/enhancements/enhancements_persistable_state'; -import { transformEnhancementsOut } from '../common/bwc/enhancements/transform_enhancements_out'; +import type { EmbeddableTransforms } from '../common'; +import type { DrilldownSetup, DrilldownState } from './drilldowns/types'; +import { getDrilldownRegistry } from './drilldowns/registry'; +import type { EmbeddableTransformsSetup } from './embeddable_transforms/types'; +import { getTransformsRegistry } from './embeddable_transforms/registry'; export interface EmbeddableSetup extends PersistableStateService { registerEmbeddableFactory: (factory: EmbeddableRegistryDefinition) => void; + /* + * Use registerDrilldown to register transforms and schema for a drilldown type. + */ + registerDrilldown: < + StoredState extends DrilldownState = DrilldownState, + State extends DrilldownState = DrilldownState + >( + type: string, + drilldown: DrilldownSetup + ) => void; /* * Use registerTransforms to register transforms and schema for an embeddable type. * Transforms decouple REST API state from stored state, @@ -43,39 +51,37 @@ export interface EmbeddableSetup extends PersistableStateService) => void; + registerTransforms: (type: string, transforms: EmbeddableTransformsSetup) => void; getAllMigrations: () => MigrateFunctionsObject; - transformEnhancementsIn: TransformEnhancementsIn; - transformEnhancementsOut: TransformEnhancementsOut; } export type EmbeddableStart = PersistableStateService & { /** * Returns all embeddable schemas registered with registerTransforms. */ - getEmbeddableSchemas: () => ObjectType[]; + getAllEmbeddableSchemas: () => ObjectType[]; - getTransforms: (type: string) => EmbeddableTransforms | undefined; + getTransforms: (type: string) => + | (EmbeddableTransforms & { + schema?: Type; + throwOnUnmappedPanel?: EmbeddableTransformsSetup['throwOnUnmappedPanel']; + }) + | undefined; }; export class EmbeddableServerPlugin implements Plugin { private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map(); private migrateFn: PersistableStateMigrateFn | undefined; - private transformsRegistry: { [key: string]: EmbeddableTransforms } = {}; + private drilldownRegistry = getDrilldownRegistry(); + private transformsRegistry = getTransformsRegistry(this.drilldownRegistry); public setup(core: CoreSetup) { this.migrateFn = getMigrateFunction(this.getEmbeddableFactory); return { registerEmbeddableFactory: this.registerEmbeddableFactory, - registerTransforms: (type: string, transforms: EmbeddableTransforms) => { - if (this.transformsRegistry[type]) { - throw new Error(`Embeddable transforms for type "${type}" are already registered.`); - } - - this.transformsRegistry[type] = transforms; - }, - transformEnhancementsIn: enhancementsPersistableState.extract, - transformEnhancementsOut, + registerDrilldown: this.drilldownRegistry + .registerDrilldown as EmbeddableSetup['registerDrilldown'], + registerTransforms: this.transformsRegistry.registerTransforms, telemetry: getTelemetryFunction(this.getEmbeddableFactory), extract: getExtractFunction(this.getEmbeddableFactory), inject: getInjectFunction(this.getEmbeddableFactory), @@ -86,13 +92,8 @@ export class EmbeddableServerPlugin implements Plugin - Object.values(this.transformsRegistry) - .map((transforms) => transforms?.getSchema?.()) - .filter((schema) => Boolean(schema)) as ObjectType[], - getTransforms: (type: string) => { - return this.transformsRegistry[type]; - }, + getAllEmbeddableSchemas: this.transformsRegistry.getAllEmbeddableSchemas, + getTransforms: this.transformsRegistry.getEmbeddableTransforms, telemetry: getTelemetryFunction(this.getEmbeddableFactory), extract: getExtractFunction(this.getEmbeddableFactory), inject: getInjectFunction(this.getEmbeddableFactory), diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.test.ts b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.test.ts index b7e8ed9637c4f..8ed828201b227 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.test.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.test.ts @@ -9,29 +9,45 @@ import type { SerializedVis } from '../../types'; import { getTransformIn } from './get_transform_in'; -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; describe('getTransformIn', () => { - const transformEnhancementsInMock = jest.fn().mockReturnValue({ - state: { dynamiceActions: 'transformedInValue' }, - references: [ - { - id: '5678', - name: 'someRef', - type: 'testType', + const drilldown = { + dashboard_id: '5678', + type: 'dashboard_drilldown', + label: 'Go to dashboard', + trigger: 'some_action', + }; + const transformDrilldownsIn = jest.fn((state) => { + const { dashboard_id, ...restOfDrilldown } = drilldown; + return { + state: { + ...state, + drilldowns: [ + { + ...restOfDrilldown, + // hardcoding reference extraction + // production code would get id from reference matching dashboardRefName + dashboardRefName: 'someRef', + }, + ], }, - ], + references: [ + { + id: '5678', + name: 'someRef', + type: 'dashboard', + }, + ], + }; }); - const transformIn = getTransformIn(transformEnhancementsInMock); + const transformIn = getTransformIn(transformDrilldownsIn); describe('by reference', () => { test('should extract references', () => { expect( transformIn({ - enhancements: { - dynamiceActions: 'originalValue', - } as unknown as DynamicActionsSerializedState['enhancements'], + drilldowns: [drilldown], savedObjectId: '1234', timeRange: { from: '15-now', to: 'now' }, title: 'custom title', @@ -48,13 +64,18 @@ describe('getTransformIn', () => { Object { "id": "5678", "name": "someRef", - "type": "testType", + "type": "dashboard", }, ], "state": Object { - "enhancements": Object { - "dynamiceActions": "transformedInValue", - }, + "drilldowns": Array [ + Object { + "dashboardRefName": "someRef", + "label": "Go to dashboard", + "trigger": "some_action", + "type": "dashboard_drilldown", + }, + ], "timeRange": Object { "from": "15-now", "to": "now", @@ -71,9 +92,7 @@ describe('getTransformIn', () => { test('should extract references', () => { expect( transformIn({ - enhancements: { - dynamiceActions: 'originalValue', - } as unknown as DynamicActionsSerializedState['enhancements'], + drilldowns: [drilldown], savedVis: { data: { searchSource: { @@ -96,13 +115,18 @@ describe('getTransformIn', () => { Object { "id": "5678", "name": "someRef", - "type": "testType", + "type": "dashboard", }, ], "state": Object { - "enhancements": Object { - "dynamiceActions": "transformedInValue", - }, + "drilldowns": Array [ + Object { + "dashboardRefName": "someRef", + "label": "Go to dashboard", + "trigger": "some_action", + "type": "dashboard_drilldown", + }, + ], "savedVis": Object { "data": Object { "searchSource": Object { diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.ts b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.ts index df9586dbe0bce..d033403deaa2b 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_in.ts @@ -7,9 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; import type { Reference } from '@kbn/content-management-utils'; import { VISUALIZE_SAVED_OBJECT_TYPE } from '@kbn/visualizations-common'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { VisualizeByReferenceState, VisualizeByValueState, @@ -24,24 +24,19 @@ import type { export const VIS_SAVED_OBJECT_REF_NAME = 'savedObjectRef'; -export function getTransformIn( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'] -) { +export function getTransformIn(transformDrilldownsIn: DrilldownTransforms['transformIn']) { function transformIn(state: VisualizeEmbeddableState): { state: StoredVisualizeEmbeddableState; references: Reference[]; } { - const enhancementsResults = state.enhancements - ? transformEnhancementsIn(state.enhancements) - : { state: undefined, references: [] }; + const { state: storedState, references: drilldownReferences } = transformDrilldownsIn(state); // by ref - if ((state as VisualizeByReferenceState).savedObjectId) { - const { savedObjectId, ...rest } = state as VisualizeByReferenceState; + if ((storedState as VisualizeByReferenceState).savedObjectId) { + const { savedObjectId, ...rest } = storedState as VisualizeByReferenceState; return { state: { ...rest, - ...(enhancementsResults.state ? { enhancements: enhancementsResults.state } : {}), } as StoredVisualizeByReferenceState, references: [ { @@ -49,33 +44,31 @@ export function getTransformIn( type: VISUALIZE_SAVED_OBJECT_TYPE, id: savedObjectId!, }, - ...enhancementsResults.references, + ...drilldownReferences, ], }; } // by value - if ((state as VisualizeByValueState).savedVis) { + if ((storedState as VisualizeByValueState).savedVis) { const { references, savedVis } = extractVisReferences( - (state as VisualizeByValueState).savedVis + (storedState as VisualizeByValueState).savedVis ); return { state: { - ...state, - ...(enhancementsResults.state ? { enhancements: enhancementsResults.state } : {}), + ...storedState, savedVis, } as StoredVisualizeByValueState, - references: [...references, ...enhancementsResults.references], + references: [...references, ...drilldownReferences], }; } return { state: { - ...state, - ...(enhancementsResults.state ? { enhancements: enhancementsResults.state } : {}), + ...storedState, } as StoredVisualizeEmbeddableState, - references: enhancementsResults.references, + references: drilldownReferences, }; } return transformIn; diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.test.ts b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.test.ts index 0dac246d352c0..c0502c0701872 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.test.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.test.ts @@ -7,14 +7,36 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; import { getTransformOut } from './get_transform_out'; import type { StoredVis } from './types'; describe('getTransformOut', () => { - const transformEnhancementsOutMock = jest - .fn() - .mockReturnValue({ dynamiceActions: 'transformedOutValue' }); + const storedDrilldown = { + dashboardRefName: 'someRef', + type: 'dashboard_drilldown', + label: 'Go to dashboard', + trigger: 'some_trigger', + }; + const drilldownReference = { + id: '5678', + name: 'someRef', + type: 'dashboard', + }; + + const transformEnhancementsOutMock = jest.fn((state, references) => { + const { dashboardRefName, ...restOfDrilldown } = storedDrilldown; + return { + ...state, + drilldowns: [ + { + ...restOfDrilldown, + // hardcoding reference injection + // production code would get id from reference matching dashboardRefName + dashboard_id: 'someRef', + }, + ], + }; + }); const transformOut = getTransformOut(transformEnhancementsOutMock); @@ -23,9 +45,7 @@ describe('getTransformOut', () => { expect( transformOut( { - enhancements: { - dynamiceActions: 'originalValue', - } as unknown as DynamicActionsSerializedState['enhancements'], + drilldowns: [storedDrilldown], timeRange: { from: '15-now', to: 'now' }, title: 'custom title', uiState: 'someUiState', @@ -36,18 +56,19 @@ describe('getTransformOut', () => { name: 'savedObjectRef', type: 'visualization', }, - { - id: '5678', - name: 'someRef', - type: 'testType', - }, + drilldownReference, ] ) ).toMatchInlineSnapshot(` Object { - "enhancements": Object { - "dynamiceActions": "transformedOutValue", - }, + "drilldowns": Array [ + Object { + "dashboard_id": "someRef", + "label": "Go to dashboard", + "trigger": "some_trigger", + "type": "dashboard_drilldown", + }, + ], "savedObjectId": "1234", "timeRange": Object { "from": "15-now", @@ -65,9 +86,7 @@ describe('getTransformOut', () => { expect( transformOut( { - enhancements: { - dynamiceActions: 'originalValue', - } as unknown as DynamicActionsSerializedState['enhancements'], + drilldowns: [storedDrilldown], savedVis: { data: { searchSource: { @@ -84,18 +103,19 @@ describe('getTransformOut', () => { name: 'kibanaSavedObjectMeta.searchSourceJSON.index', type: 'index-pattern', }, - { - id: '5678', - name: 'someRef', - type: 'testType', - }, + drilldownReference, ] ) ).toMatchInlineSnapshot(` Object { - "enhancements": Object { - "dynamiceActions": "transformedOutValue", - }, + "drilldowns": Array [ + Object { + "dashboard_id": "someRef", + "label": "Go to dashboard", + "trigger": "some_trigger", + "type": "dashboard_drilldown", + }, + ], "savedVis": Object { "data": Object { "searchSource": Object { diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.ts b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.ts index cdff5cedc7aa9..188bdf5ecf63e 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transform_out.ts @@ -9,20 +9,20 @@ import type { Reference } from '@kbn/content-management-utils/src/types'; import { transformTitlesOut } from '@kbn/presentation-publishing'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { VISUALIZE_SAVED_OBJECT_TYPE } from '@kbn/visualizations-common'; +import { flow } from 'lodash'; import { injectVisReferences } from '../../references/inject_vis_references'; import { VIS_SAVED_OBJECT_REF_NAME } from './get_transform_in'; import type { StoredVisualizeByValueState, StoredVisualizeEmbeddableState } from './types'; -export function getTransformOut( - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransformOut(transformDrilldownsOut: DrilldownTransforms['transformOut']) { function transformOut(storedState: StoredVisualizeEmbeddableState, references?: Reference[]) { - const state = transformTitlesOut(storedState); - const enhancementsState = state.enhancements - ? transformEnhancementsOut(state.enhancements, references ?? []) - : undefined; + const transformsFlow = flow( + transformTitlesOut, + (state: StoredVisualizeEmbeddableState) => transformDrilldownsOut(state, references) + ); + const state = transformsFlow(storedState); // by ref const savedObjectRef = (references ?? []).find( @@ -31,7 +31,6 @@ export function getTransformOut( if (savedObjectRef) { return { ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), savedObjectId: savedObjectRef.id, }; } @@ -45,15 +44,11 @@ export function getTransformOut( return { ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), savedVis, }; } - return { - ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), - }; + return state; } return transformOut; } diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transforms.ts b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transforms.ts index fb72c52829d96..71a8528a01ffc 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transforms.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/transforms/get_transforms.ts @@ -7,16 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { getTransformIn } from './get_transform_in'; import { getTransformOut } from './get_transform_out'; -export function getTransforms( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'], - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransforms(drilldownTransforms: DrilldownTransforms) { return { - transformIn: getTransformIn(transformEnhancementsIn), - transformOut: getTransformOut(transformEnhancementsOut), + transformIn: getTransformIn(drilldownTransforms.transformIn), + transformOut: getTransformOut(drilldownTransforms.transformOut), }; } diff --git a/src/platform/plugins/shared/visualizations/common/embeddable/types.ts b/src/platform/plugins/shared/visualizations/common/embeddable/types.ts index 90eb19202ef87..0fe9e1929443f 100644 --- a/src/platform/plugins/shared/visualizations/common/embeddable/types.ts +++ b/src/platform/plugins/shared/visualizations/common/embeddable/types.ts @@ -7,14 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; import type { SerializedTimeRange, SerializedTitles } from '@kbn/presentation-publishing'; import type { VisParams } from '@kbn/visualizations-common'; +import type { DrilldownsState } from '@kbn/embeddable-plugin/server'; import type { SerializedVis } from '../types'; -export type VisualizeEmbeddableBaseState = SerializedTitles & - SerializedTimeRange & - Partial; +export type VisualizeEmbeddableBaseState = SerializedTitles & SerializedTimeRange & DrilldownsState; export type VisualizeByReferenceState = VisualizeEmbeddableBaseState & { savedObjectId?: string; diff --git a/src/platform/plugins/shared/visualizations/public/embeddable/state.ts b/src/platform/plugins/shared/visualizations/public/embeddable/state.ts index cb5945c6b11a0..3b5973fd27b1c 100644 --- a/src/platform/plugins/shared/visualizations/public/embeddable/state.ts +++ b/src/platform/plugins/shared/visualizations/public/embeddable/state.ts @@ -52,7 +52,7 @@ export const deserializeState = async (state: VisualizeEmbeddableState | undefin export const deserializeSavedObjectState = async ({ savedObjectId, - enhancements, + drilldowns, uiState, timeRange, title: embeddableTitle, @@ -107,7 +107,7 @@ export const deserializeSavedObjectState = async ({ savedObjectProperties, linkedToLibrary: true, ...(timeRange ? { timeRange } : {}), - ...(enhancements ? { enhancements } : {}), + ...(drilldowns ? { drilldowns } : {}), } as VisualizeRuntimeState; }; diff --git a/src/platform/plugins/shared/visualizations/public/embeddable/types.ts b/src/platform/plugins/shared/visualizations/public/embeddable/types.ts index 7992b73627a58..cda9491ca5a17 100644 --- a/src/platform/plugins/shared/visualizations/public/embeddable/types.ts +++ b/src/platform/plugins/shared/visualizations/public/embeddable/types.ts @@ -8,7 +8,6 @@ */ import type { OverlayRef } from '@kbn/core-mount-utils-browser'; -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; import type { TimeRange } from '@kbn/es-query'; import type { HasInspectorAdapters } from '@kbn/inspector-plugin/public'; @@ -27,6 +26,7 @@ import type { } from '@kbn/presentation-publishing'; import type { DeepPartial } from '@kbn/utility-types'; import type { VisParams } from '@kbn/visualizations-common'; +import type { DrilldownsState } from '@kbn/embeddable-plugin/server'; import type { VisualizeEmbeddableState } from '../../common/embeddable/types'; import type { HasVisualizeConfig } from './interfaces/has_visualize_config'; import type { Vis, VisSavedObject } from '../types'; @@ -44,7 +44,7 @@ export type ExtraSavedObjectProperties = Pick< export type VisualizeRuntimeState = SerializedTitles & SerializedTimeRange & - Partial & { + DrilldownsState & { serializedVis: SerializedVis; savedObjectId?: string; savedObjectProperties?: ExtraSavedObjectProperties; diff --git a/src/platform/plugins/shared/visualizations/public/embeddable/visualize_embeddable.tsx b/src/platform/plugins/shared/visualizations/public/embeddable/visualize_embeddable.tsx index d1fe58168b985..9dea1688147da 100644 --- a/src/platform/plugins/shared/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/platform/plugins/shared/visualizations/public/embeddable/visualize_embeddable.tsx @@ -70,7 +70,7 @@ export const getVisualizeEmbeddableFactory: (deps: { const titleManager = initializeTitleManager(initialState); // Initialize dynamic actions - const dynamicActionsManager = embeddableEnhancedStart?.initializeEmbeddableDynamicActions( + const dynamicActionsManager = await embeddableEnhancedStart?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -206,7 +206,7 @@ export const getVisualizeEmbeddableFactory: (deps: { ).pipe(map(() => undefined)), getComparators: () => { return { - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { drilldowns: 'skip', enhancements: 'skip' }), ...titleComparators, ...timeRangeComparators, savedObjectId: 'skip', diff --git a/src/platform/plugins/shared/visualizations/public/plugin.ts b/src/platform/plugins/shared/visualizations/public/plugin.ts index 24e61117657fc..c07353fbdaee5 100644 --- a/src/platform/plugins/shared/visualizations/public/plugin.ts +++ b/src/platform/plugins/shared/visualizations/public/plugin.ts @@ -68,6 +68,7 @@ import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plu import { css, injectGlobal } from '@emotion/css'; import { VisualizeConstants, VISUALIZE_EMBEDDABLE_TYPE } from '@kbn/visualizations-common'; import type { KqlPluginStart } from '@kbn/kql/public'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { TypesSetup, TypesStart } from './vis_types'; import type { VisualizeServices } from './visualize_app/types'; import { @@ -493,10 +494,13 @@ export class VisualizationsPlugin return getTypes().get(visState.type)?.icon ?? ''; }, }); - embeddable.registerLegacyURLTransform(VISUALIZE_EMBEDDABLE_TYPE, async () => { - const { getTransformOut } = await import('./embeddable/embeddable_module'); - return getTransformOut(embeddable.transformEnhancementsOut); - }); + embeddable.registerLegacyURLTransform( + VISUALIZE_EMBEDDABLE_TYPE, + async (transformDrilldownsOut: DrilldownTransforms['transformOut']) => { + const { getTransformOut } = await import('./embeddable/embeddable_module'); + return getTransformOut(transformDrilldownsOut); + } + ); contentManagement.registry.register({ id: CONTENT_ID, diff --git a/src/platform/plugins/shared/visualizations/server/plugin.ts b/src/platform/plugins/shared/visualizations/server/plugin.ts index c73a54321b651..111b4a84e3d14 100644 --- a/src/platform/plugins/shared/visualizations/server/plugin.ts +++ b/src/platform/plugins/shared/visualizations/server/plugin.ts @@ -56,13 +56,7 @@ export class VisualizationsPlugin makeVisualizeEmbeddableFactory(getSearchSourceMigrations)() ); - plugins.embeddable.registerTransforms( - VISUALIZE_EMBEDDABLE_TYPE, - getTransforms( - plugins.embeddable.transformEnhancementsIn, - plugins.embeddable.transformEnhancementsOut - ) - ); + plugins.embeddable.registerTransforms(VISUALIZE_EMBEDDABLE_TYPE, { getTransforms }); plugins.contentManagement.register({ id: CONTENT_ID, diff --git a/x-pack/platform/plugins/private/data_visualizer/server/plugin.ts b/x-pack/platform/plugins/private/data_visualizer/server/plugin.ts index 05d57cd76466b..652eadefdf3b3 100644 --- a/x-pack/platform/plugins/private/data_visualizer/server/plugin.ts +++ b/x-pack/platform/plugins/private/data_visualizer/server/plugin.ts @@ -34,8 +34,10 @@ export class DataVisualizerPlugin implements Plugin ({ + transformIn, + transformOut, + }), }); } diff --git a/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts index ad30901dc1210..dbe3145912bbe 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts @@ -70,6 +70,7 @@ test.describe( ).toStrictEqual([ 'aiops', 'discover', + 'embeddableEnhanced', 'eventAnnotation', 'expressionXY', 'kbn-ui-shared-deps-npm', diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/common/constants.ts b/x-pack/platform/plugins/private/drilldowns/url_drilldown/common/constants.ts new file mode 100644 index 0000000000000..a4845bff7f335 --- /dev/null +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/common/constants.ts @@ -0,0 +1,29 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CONTEXT_MENU_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '@kbn/embeddable-plugin/common'; +import { IMAGE_CLICK_TRIGGER } from '@kbn/image-embeddable-plugin/common'; +import { ROW_CLICK_TRIGGER } from '@kbn/ui-actions-browser'; + +// Do not change constant value - part of public REST API +export const URL_DRILLDOWN_TYPE = 'url_drilldown'; + +// Only additive changes are allowed, part of public REST API +export const URL_DRILLDOWN_SUPPORTED_TRIGGERS = [ + VALUE_CLICK_TRIGGER, + SELECT_RANGE_TRIGGER, + ROW_CLICK_TRIGGER, + CONTEXT_MENU_TRIGGER, + IMAGE_CLICK_TRIGGER, +]; + +export const DEFAULT_ENCODE_URL = true; +export const DEFAULT_OPEN_IN_NEW_TAB = true; diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/kibana.jsonc b/x-pack/platform/plugins/private/drilldowns/url_drilldown/kibana.jsonc index 05083a54a7080..6501508cb9078 100644 --- a/x-pack/platform/plugins/private/drilldowns/url_drilldown/kibana.jsonc +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/kibana.jsonc @@ -10,7 +10,7 @@ "plugin": { "id": "urlDrilldown", "browser": true, - "server": false, + "server": true, "requiredPlugins": [ "embeddable", "uiActionsEnhanced" diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/moon.yml b/x-pack/platform/plugins/private/drilldowns/url_drilldown/moon.yml index 78573213f97e9..5c78f0cc6fe10 100644 --- a/x-pack/platform/plugins/private/drilldowns/url_drilldown/moon.yml +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/moon.yml @@ -34,6 +34,8 @@ dependsOn: - '@kbn/core-ui-settings-browser' - '@kbn/core-theme-browser-mocks' - '@kbn/presentation-publishing' + - '@kbn/config-schema' + - '@kbn/ui-actions-browser' tags: - plugin - prod @@ -42,7 +44,9 @@ tags: - jest-unit-tests fileGroups: src: + - common/**/* - public/**/* + - server/**/* - '!target/**/*' tasks: jest: diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/platform/plugins/private/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index b6b04ad396bf0..9b3176d4db754 100644 --- a/x-pack/platform/plugins/private/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -39,6 +39,11 @@ import type { SerializedAction } from '@kbn/ui-actions-enhanced-plugin/common/ty import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { EuiText, EuiTextBlockTruncate } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { + DEFAULT_ENCODE_URL, + DEFAULT_OPEN_IN_NEW_TAB, + URL_DRILLDOWN_SUPPORTED_TRIGGERS, +} from '../../common/constants'; import { txtUrlDrilldownDisplayName } from './i18n'; import { getEventScopeValues, getEventVariableList } from './variables/event_variables'; import { getContextScopeValues, getContextVariableList } from './variables/context_variables'; @@ -130,13 +135,7 @@ export class UrlDrilldown implements Drilldown = ({ config, onConfig, context }) => { const [variables, exampleUrl] = React.useMemo( @@ -167,8 +166,8 @@ export class UrlDrilldown implements Drilldown { @@ -210,7 +209,7 @@ export class UrlDrilldown implements Drilldown { + const { UrlDrilldownPlugin } = await import('./plugin'); + return new UrlDrilldownPlugin(); +}; diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/plugin.ts b/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/plugin.ts new file mode 100644 index 0000000000000..cf0057c0a21fc --- /dev/null +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/plugin.ts @@ -0,0 +1,31 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { URL_DRILLDOWN_SUPPORTED_TRIGGERS, URL_DRILLDOWN_TYPE } from '../common/constants'; +import { urlDrilldownSchema } from './schemas'; + +interface SetupDependencies { + embeddable: EmbeddableSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface StartDependencies {} + +export class UrlDrilldownPlugin + implements Plugin +{ + public setup(core: CoreSetup, { embeddable }: SetupDependencies) { + embeddable.registerDrilldown(URL_DRILLDOWN_TYPE, { + schema: urlDrilldownSchema, + supportedTriggers: URL_DRILLDOWN_SUPPORTED_TRIGGERS, + }); + } + + public start(core: CoreStart) {} +} diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/schemas.ts b/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/schemas.ts new file mode 100644 index 0000000000000..ff675237009fe --- /dev/null +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/server/schemas.ts @@ -0,0 +1,27 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { DEFAULT_ENCODE_URL, DEFAULT_OPEN_IN_NEW_TAB } from '../common/constants'; + +export const urlDrilldownSchema = schema.object({ + encode_url: schema.boolean({ + defaultValue: DEFAULT_ENCODE_URL, + meta: { + description: 'When true, URL is escaped using percent encoding', + }, + }), + open_in_new_tab: schema.boolean({ + defaultValue: DEFAULT_OPEN_IN_NEW_TAB, + }), + url: schema.string({ + meta: { + description: + 'Templated Url. Variables documented at https://www.elastic.co/docs/explore-analyze/dashboards/drilldowns#url-template-variable', + }, + }), +}); diff --git a/x-pack/platform/plugins/private/drilldowns/url_drilldown/tsconfig.json b/x-pack/platform/plugins/private/drilldowns/url_drilldown/tsconfig.json index d552b333de877..a52b02c4360f0 100644 --- a/x-pack/platform/plugins/private/drilldowns/url_drilldown/tsconfig.json +++ b/x-pack/platform/plugins/private/drilldowns/url_drilldown/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types", }, - "include": ["public/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", "@kbn/ui-actions-enhanced-plugin", @@ -20,7 +20,9 @@ "@kbn/core-ui-settings-browser-mocks", "@kbn/core-ui-settings-browser", "@kbn/core-theme-browser-mocks", - "@kbn/presentation-publishing" + "@kbn/presentation-publishing", + "@kbn/config-schema", + "@kbn/ui-actions-browser" ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/shared/aiops/server/plugin.ts b/x-pack/platform/plugins/shared/aiops/server/plugin.ts index d9996b141c94d..7e56679967301 100755 --- a/x-pack/platform/plugins/shared/aiops/server/plugin.ts +++ b/x-pack/platform/plugins/shared/aiops/server/plugin.ts @@ -84,18 +84,24 @@ export class AiopsPlugin }); plugins.embeddable.registerTransforms(EMBEDDABLE_CHANGE_POINT_CHART_TYPE, { - transformIn: changePointTransformIn, - transformOut: changePointTransformOut, + getTransforms: () => ({ + transformIn: changePointTransformIn, + transformOut: changePointTransformOut, + }), }); plugins.embeddable.registerTransforms(EMBEDDABLE_PATTERN_ANALYSIS_TYPE, { - transformIn: patternAnalysisTransformIn, - transformOut: patternAnalysisTransformOut, + getTransforms: () => ({ + transformIn: patternAnalysisTransformIn, + transformOut: patternAnalysisTransformOut, + }), }); plugins.embeddable.registerTransforms(EMBEDDABLE_LOG_RATE_ANALYSIS_TYPE, { - transformIn: logRateTransformIn, - transformOut: logRateTransformOut, + getTransforms: () => ({ + transformIn: logRateTransformIn, + transformOut: logRateTransformOut, + }), }); return {}; diff --git a/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/bwc.ts b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/bwc.ts new file mode 100644 index 0000000000000..f39c0c6027975 --- /dev/null +++ b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/bwc.ts @@ -0,0 +1,144 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; +import type { DynamicActionsSerializedState } from './types'; + +// +// Temporary work around - REST APIs use DrilldownState but client still uses EnhancementsState +// Remove when client code supports DrilldownState +// + +export function extractEnhancements(state: DynamicActionsSerializedState) { + if (!state.drilldowns || !state.drilldowns.length) { + return {}; + } + + return { + dynamicActions: { + events: state.drilldowns + .map((drilldown) => { + if (drilldown.type === 'dashboard_drilldown') { + const { dashboard_id, open_in_new_tab, use_filters, use_time_range } = + drilldown as unknown as { + dashboard_id: string; + open_in_new_tab?: boolean; + use_filters?: boolean; + use_time_range?: boolean; + }; + return { + action: { + config: { + dashboardId: dashboard_id, + open_in_new_tab: open_in_new_tab ?? false, + use_time_range: use_time_range ?? true, + use_filters: use_filters ?? true, + }, + factoryId: 'DASHBOARD_TO_DASHBOARD_DRILLDOWN', + name: drilldown.label ?? '', + }, + eventId: uuidv4(), + triggers: [drilldown.trigger], + }; + } + + if (drilldown.type === 'discover_drilldown') { + const { open_in_new_tab } = drilldown as unknown as { + open_in_new_tab?: boolean; + }; + return { + action: { + config: { + openInNewTab: open_in_new_tab ?? false, + }, + factoryId: 'OPEN_IN_DISCOVER_DRILLDOWN', + name: drilldown.label ?? '', + }, + eventId: uuidv4(), + triggers: [drilldown.trigger], + }; + } + + if (drilldown.type === 'url_drilldown') { + const { encode_url, open_in_new_tab, url } = drilldown as unknown as { + encode_url?: boolean; + open_in_new_tab?: boolean; + url: string; + }; + return { + action: { + config: { + encodeUrl: encode_url ?? true, + openInNewTab: open_in_new_tab ?? false, + url: { + template: url, + }, + }, + factoryId: 'URL_DRILLDOWN', + name: drilldown.label ?? '', + }, + eventId: uuidv4(), + triggers: [drilldown.trigger], + }; + } + }) + .filter((event) => event !== undefined), + }, + }; +} + +export function serializeEnhancements(enhancements: DynamicActionsSerializedState['enhancements']) { + if (!enhancements?.dynamicActions?.events.length) { + return {}; + } + + const drilldowns = enhancements.dynamicActions.events + .map((event) => { + if (event.action.factoryId === 'DASHBOARD_TO_DASHBOARD_DRILLDOWN') { + const { dashboardId, openInNewTab, useCurrentDateRange, useCurrentFilters } = + event.action.config; + return { + dashboard_id: dashboardId, + label: event.action.name, + open_in_new_tab: openInNewTab ?? false, + trigger: event.triggers[0] ?? 'unknown', + type: 'dashboard_drilldown', + use_time_range: useCurrentDateRange ?? true, + use_filters: useCurrentFilters ?? true, + }; + } + + if (event.action.factoryId === 'OPEN_IN_DISCOVER_DRILLDOWN') { + const { openInNewTab } = event.action.config; + return { + label: event.action.name, + open_in_new_tab: openInNewTab ?? false, + trigger: event.triggers[0] ?? 'unknown', + type: 'discover_drilldown', + }; + } + + if (event.action.factoryId === 'URL_DRILLDOWN') { + const { encodeUrl, openInNewTab, url } = event.action.config as { + encodeUrl?: boolean; + openInNewTab?: boolean; + url?: { template?: string }; + }; + return { + label: event.action.name, + encode_url: encodeUrl ?? true, + open_in_new_tab: openInNewTab ?? true, + trigger: event.triggers[0] ?? 'unknown', + type: 'url_drilldown', + url: url?.template ?? '', + }; + } + }) + .filter((drilldown) => drilldown !== undefined); + + return drilldowns.length ? { drilldowns } : {}; +} diff --git a/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/dynamic_actions_manager.ts b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/dynamic_actions_manager.ts index ba058efde497d..8dabd7a924e52 100644 --- a/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/dynamic_actions_manager.ts +++ b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/dynamic_actions_manager.ts @@ -14,6 +14,7 @@ import { DynamicActionStorage, type DynamicActionStorageApi } from './dynamic_ac import { getDynamicActionsState } from './get_dynamic_actions_state'; import type { DynamicActionsSerializedState, EmbeddableDynamicActionsManager } from './types'; import type { StartDependencies } from '../plugin'; +import { extractEnhancements, serializeEnhancements } from './bwc'; export function initializeDynamicActionsManager( uuid: string, @@ -22,12 +23,13 @@ export function initializeDynamicActionsManager( services: StartDependencies ): EmbeddableDynamicActionsManager { const dynamicActionsState$ = new BehaviorSubject( - getDynamicActionsState(state.enhancements) + getDynamicActionsState(extractEnhancements(state)) ); const api: DynamicActionStorageApi = { dynamicActionsState$, setDynamicActions: (enhancements) => { dynamicActionsState$.next(getDynamicActionsState(enhancements)); + storage.reload$.next(); }, }; const storage = new DynamicActionStorage(uuid, getTitle, api); @@ -41,21 +43,20 @@ export function initializeDynamicActionsManager( }); function getLatestState() { - return { enhancements: dynamicActionsState$.getValue() }; + return serializeEnhancements(dynamicActionsState$.getValue()); } return { api: { ...api, enhancements: { dynamicActions } }, comparators: { - enhancements: (a, b) => { - return deepEqual(getDynamicActionsState(a), getDynamicActionsState(b)); - }, + enhancements: 'skip', + drilldowns: (a, b) => deepEqual(a, b), } as StateComparators, anyStateChange$: dynamicActionsState$.pipe(map(() => undefined)), getLatestState, serializeState: () => getLatestState(), reinitializeState: (lastState: DynamicActionsSerializedState) => { - api.setDynamicActions(lastState.enhancements); + api.setDynamicActions(getDynamicActionsState(extractEnhancements(lastState))); }, startDynamicActions: () => { dynamicActions.start().catch((error) => { diff --git a/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/types.ts b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/types.ts index 643e29b382389..32d3860539102 100644 --- a/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/types.ts +++ b/x-pack/platform/plugins/shared/embeddable_enhanced/public/embeddables/types.ts @@ -8,6 +8,7 @@ import type { StateComparators } from '@kbn/presentation-publishing'; import type { DynamicActionsState } from '@kbn/ui-actions-enhanced-plugin/public'; import type { Observable } from 'rxjs'; +import type { DrilldownsState } from '@kbn/embeddable-plugin/server'; import type { HasDynamicActions } from './interfaces/has_dynamic_actions'; export interface EmbeddableDynamicActionsManager { @@ -20,6 +21,6 @@ export interface EmbeddableDynamicActionsManager { startDynamicActions: () => { stopDynamicActions: () => void }; } -export interface DynamicActionsSerializedState { +export type DynamicActionsSerializedState = DrilldownsState & { enhancements?: { dynamicActions: DynamicActionsState }; -} +}; diff --git a/x-pack/platform/plugins/shared/embeddable_enhanced/public/plugin.ts b/x-pack/platform/plugins/shared/embeddable_enhanced/public/plugin.ts index 49c917956a24d..6f79b4ae2a1e4 100644 --- a/x-pack/platform/plugins/shared/embeddable_enhanced/public/plugin.ts +++ b/x-pack/platform/plugins/shared/embeddable_enhanced/public/plugin.ts @@ -15,7 +15,6 @@ import type { DynamicActionsSerializedState, EmbeddableDynamicActionsManager, } from './embeddables/types'; -import { initializeDynamicActionsManager } from './embeddables/dynamic_actions_manager'; export interface SetupDependencies { embeddable: EmbeddableSetup; @@ -35,7 +34,7 @@ export interface StartContract { uuid: string, getTitle: () => string | undefined, state: DynamicActionsSerializedState - ) => EmbeddableDynamicActionsManager; + ) => Promise; } export class EmbeddableEnhancedPlugin @@ -49,11 +48,14 @@ export class EmbeddableEnhancedPlugin public start(core: CoreStart, plugins: StartDependencies): StartContract { return { - initializeEmbeddableDynamicActions: ( + initializeEmbeddableDynamicActions: async ( uuid: string, getTitle: () => string | undefined, state: DynamicActionsSerializedState ) => { + const { initializeDynamicActionsManager } = await import( + './embeddables/dynamic_actions_manager' + ); return initializeDynamicActionsManager(uuid, getTitle, state, plugins); }, }; diff --git a/x-pack/platform/plugins/shared/lens/common/constants.ts b/x-pack/platform/plugins/shared/lens/common/constants.ts index 837ab4453e1bb..99a2eaec135ba 100644 --- a/x-pack/platform/plugins/shared/lens/common/constants.ts +++ b/x-pack/platform/plugins/shared/lens/common/constants.ts @@ -6,7 +6,11 @@ */ import rison from '@kbn/rison'; -import type { RefreshInterval, TimeRange } from '@kbn/data-plugin/common'; +import { + APPLY_FILTER_TRIGGER, + type RefreshInterval, + type TimeRange, +} from '@kbn/data-plugin/common'; import type { Filter } from '@kbn/es-query'; export const PLUGIN_ID = 'lens'; @@ -97,3 +101,8 @@ export function getFullPath(id?: string) { } export const COLOR_MAPPING_OFF_BY_DEFAULT = false; + +// Do not change constan value - part of public REST API +export const DISCOVER_DRILLDOWN_TYPE = 'discover_drilldown'; +// Only additive changes are allowed, part of public REST API +export const DISCOVER_DRILLDOWN_SUPPORTED_TRIGGERS = [APPLY_FILTER_TRIGGER]; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts deleted file mode 100644 index 2c2e19a19d1c1..0000000000000 --- a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; - -import type { LensConfigBuilder } from '@kbn/lens-embeddable-utils'; -import type { LensTransforms } from './types'; -import { getTransformIn } from './transform_in'; -import { getTransformOut } from './transform_out'; - -export interface LensTransformDependencies { - builder: LensConfigBuilder; - transformEnhancementsIn?: EmbeddableSetup['transformEnhancementsIn']; - transformEnhancementsOut?: EmbeddableSetup['transformEnhancementsOut']; -} - -export function getLensTransforms(deps: LensTransformDependencies): LensTransforms { - return { - transformIn: getTransformIn(deps), - transformOut: getTransformOut(deps), - }; -} diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/transform_in.ts b/x-pack/platform/plugins/shared/lens/common/transforms/transform_in.ts index 64f5971317c83..3557379e2edec 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/transform_in.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/transform_in.ts @@ -6,7 +6,8 @@ */ import { isLensAPIFormat } from '@kbn/lens-embeddable-utils/config_builder/utils'; -import type { LensTransformDependencies } from '.'; +import type { LensConfigBuilder } from '@kbn/lens-embeddable-utils'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { DOC_TYPE } from '../constants'; import { extractLensReferences } from '../references'; import type { @@ -20,28 +21,24 @@ import type { LensSerializedState } from '../../public'; /** * Transform from Lens API format to Lens Serialized State */ -export const getTransformIn = ({ - builder, - transformEnhancementsIn, -}: LensTransformDependencies): LensTransformIn => { +export const getTransformIn = ( + builder: LensConfigBuilder, + transformDrilldownsIn: DrilldownTransforms['transformIn'] +): LensTransformIn => { return function transformIn(config) { - const enhancementsResult = - transformEnhancementsIn && config.enhancements - ? transformEnhancementsIn(config.enhancements) - : { state: undefined, references: [] }; + const { state: storedConfig, references: drilldownReferences } = transformDrilldownsIn(config); - if (isByRefLensConfig(config)) { - const { savedObjectId: id, ...rest } = config; + if (isByRefLensConfig(storedConfig)) { + const { savedObjectId: id, ...rest } = storedConfig; return { state: rest, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), references: [ { name: LENS_SAVED_OBJECT_REF_NAME, type: DOC_TYPE, id: id!, }, - ...enhancementsResult.references, + ...drilldownReferences, ], } satisfies LensByRefTransformInResult; } @@ -49,13 +46,12 @@ export const getTransformIn = ({ const chartType = builder.getType(config.attributes); if (!builder.isSupported(chartType)) { - const { state, references } = extractLensReferences(config as LensSerializedState); + const { state, references } = extractLensReferences(storedConfig as LensSerializedState); // TODO: remove this once all formats are supported // when not supported, no transform is needed return { state, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), - references: [...references, ...enhancementsResult.references], + references: [...references, ...drilldownReferences], } satisfies LensByValueTransformInResult; } @@ -68,14 +64,13 @@ export const getTransformIn = ({ ? builder.fromAPIFormat(config.attributes) : config.attributes; const { state, references } = extractLensReferences({ - ...config, + ...storedConfig, attributes, }); return { state, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), - references: [...references, ...enhancementsResult.references], + references: [...references, ...drilldownReferences], } satisfies LensByValueTransformInResult; }; }; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts b/x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts index f4d7dddd2afa2..de7155139520b 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts @@ -5,12 +5,13 @@ * 2.0. */ -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; +import type { LensSerializedState } from '@kbn/lens-common'; import { transformTitlesOut } from '@kbn/presentation-publishing'; import { LENS_UNKNOWN_VIS, type LensByValueSerializedState } from '@kbn/lens-common'; import { LENS_ITEM_VERSION_V2 } from '@kbn/lens-common/content_management/constants'; -import type { LensAttributes } from '@kbn/lens-embeddable-utils'; -import type { LensTransformDependencies } from '.'; +import type { LensAttributes, LensConfigBuilder } from '@kbn/lens-embeddable-utils'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; +import { flow } from 'lodash'; import { transformToV1LensItemAttributes } from '../content_management/v1'; import { transformToV2LensItemAttributes } from '../content_management/v2'; import { injectLensReferences } from '../references'; @@ -25,25 +26,22 @@ import { isLensAttributesV0, isLensAttributesV1 } from '../content_management/ut /** * Transform from Lens Stored State to Lens API format */ -export const getTransformOut = ({ - builder, - transformEnhancementsOut, -}: LensTransformDependencies): LensTransformOut => { +export const getTransformOut = ( + builder: LensConfigBuilder, + transformDrilldownsOut: DrilldownTransforms['transformOut'] +): LensTransformOut => { return function transformOut(storedState, panelReferences) { - const state = transformTitlesOut(storedState); - const enhancements = state.enhancements - ? transformEnhancementsOut?.(state.enhancements, panelReferences ?? []) - : undefined; - const enhancementsState = ( - enhancements ? { enhancements } : {} - ) as DynamicActionsSerializedState; + const transformsFlow = flow( + transformTitlesOut, + (state: LensSerializedState) => transformDrilldownsOut(state, panelReferences) + ); + const state = transformsFlow(storedState); const savedObjectRef = findLensReference(panelReferences); if (savedObjectRef && isByRefLensState(state)) { return { ...state, - ...enhancementsState, savedObjectId: savedObjectRef.id, } satisfies LensByRefTransformOutResult; } @@ -52,7 +50,6 @@ export const getTransformOut = ({ const injectedState = injectLensReferences( { ...state, - ...enhancementsState, attributes: migratedAttributes, }, panelReferences diff --git a/x-pack/platform/plugins/shared/lens/public/async_services.ts b/x-pack/platform/plugins/shared/lens/public/async_services.ts index 8931a358ecf57..31a0a23f84022 100644 --- a/x-pack/platform/plugins/shared/lens/public/async_services.ts +++ b/x-pack/platform/plugins/shared/lens/public/async_services.ts @@ -14,7 +14,7 @@ * This file causes all of them to be served in a single request. */ -export { getLensTransforms } from '../common/transforms'; +export { getTransformOut } from '../common/transforms/transform_out'; export * from './visualizations/datatable/datatable_visualization'; export * from './visualizations/datatable'; diff --git a/x-pack/platform/plugins/shared/lens/public/plugin.ts b/x-pack/platform/plugins/shared/lens/public/plugin.ts index efd4aedb8a752..e3121b51d2221 100644 --- a/x-pack/platform/plugins/shared/lens/public/plugin.ts +++ b/x-pack/platform/plugins/shared/lens/public/plugin.ts @@ -84,6 +84,7 @@ import { LENS_CONTENT_TYPE, LENS_ITEM_LATEST_VERSION, } from '@kbn/lens-common/content_management/constants'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { FormBasedDatasource as FormBasedDatasourceType, @@ -406,17 +407,16 @@ export class LensPlugin { // This loads the builder async to allow synchronous access to builder via getLensBuilder await setLensBuilder(flags.apiFormat); - embeddable.registerLegacyURLTransform(LENS_EMBEDDABLE_TYPE, async () => { - const { getLensTransforms } = await import('./async_services'); - const { LensConfigBuilder } = await import('@kbn/lens-embeddable-utils'); - const builder = new LensConfigBuilder(undefined, flags.apiFormat); - - return getLensTransforms({ - builder, - transformEnhancementsIn: embeddable.transformEnhancementsIn, - transformEnhancementsOut: embeddable.transformEnhancementsOut, - }).transformOut; - }); + embeddable.registerLegacyURLTransform( + LENS_EMBEDDABLE_TYPE, + async (transformDrilldownsOut: DrilldownTransforms['transformOut']) => { + const { getTransformOut } = await import('./async_services'); + const { LensConfigBuilder } = await import('@kbn/lens-embeddable-utils'); + const builder = new LensConfigBuilder(undefined, flags.apiFormat); + + return getTransformOut(builder, transformDrilldownsOut); + } + ); }) ); diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_actions.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_actions.tsx index 3726a6c801618..e83778c21c035 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_actions.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_actions.tsx @@ -9,13 +9,12 @@ import type { Capabilities } from '@kbn/core-capabilities-common'; import { getEsQueryConfig } from '@kbn/data-plugin/public'; import type { AggregateQuery, EsQueryConfig, Filter, Query, TimeRange } from '@kbn/es-query'; import { isOfQueryType } from '@kbn/es-query'; -import type { PublishingSubject, StateComparators } from '@kbn/presentation-publishing'; +import type { PublishingSubject } from '@kbn/presentation-publishing'; import { apiPublishesProjectRouting, apiPublishesUnifiedSearch, } from '@kbn/presentation-publishing'; import type { - DynamicActionsSerializedState, EmbeddableDynamicActionsManager, HasDynamicActions, } from '@kbn/embeddable-enhanced-plugin/public'; @@ -252,8 +251,8 @@ export function initializeActionApi( ): { api: ViewInDiscoverCallbacks & HasDynamicActions; anyStateChange$: Observable; - getComparators: () => StateComparators; - getLatestState: () => DynamicActionsSerializedState; + getComparators: () => EmbeddableDynamicActionsManager['comparators']; + getLatestState: () => ReturnType; cleanup: () => void; reinitializeState: (lastSaved?: LensSerializedAPIConfig) => void; } { @@ -273,6 +272,7 @@ export function initializeActionApi( anyStateChange$: dynamicActionsManager?.anyStateChange$ ?? new BehaviorSubject(undefined), getComparators: () => ({ ...(dynamicActionsManager?.comparators ?? { + drilldowns: 'skip', enhancements: 'skip', }), }), diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/lens_embeddable.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/lens_embeddable.tsx index 12716c4c91ac9..9a72e5d3a4df2 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/lens_embeddable.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/lens_embeddable.tsx @@ -57,11 +57,12 @@ export const createLensEmbeddableFactory = ( buildEmbeddable: async ({ initialState, finalizeApi, parentApi, uuid }) => { const titleManager = initializeTitleManager(initialState); - const dynamicActionsManager = services.embeddableEnhanced?.initializeEmbeddableDynamicActions( - uuid, - () => titleManager.api.title$.getValue(), - initialState - ); + const dynamicActionsManager = + await services.embeddableEnhanced?.initializeEmbeddableDynamicActions( + uuid, + () => titleManager.api.title$.getValue(), + initialState + ); const initialRuntimeState = await deserializeState(services, initialState); diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/mocks/index.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/mocks/index.tsx index 7832e1dbc1590..d221dd00f1eff 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/mocks/index.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/mocks/index.tsx @@ -200,7 +200,7 @@ export function makeEmbeddableServices( getTrigger: jest.fn().mockImplementation(() => ({ exec: jest.fn() })), }, embeddableEnhanced: { - initializeEmbeddableDynamicActions: jest.fn(mockDynamicActionsManager), + initializeEmbeddableDynamicActions: jest.fn().mockResolvedValue(mockDynamicActionsManager), }, fieldsMetadata: fieldsMetadataPluginPublicMock.createStartContract(), }; @@ -215,8 +215,9 @@ export function mockDynamicActionsManager() { } as unknown as EmbeddableDynamicActionsManager['api'], anyStateChange$: of(undefined), comparators: { + drilldown: jest.fn(), enhancements: jest.fn(), - }, + } as unknown as EmbeddableDynamicActionsManager['comparators'], getLatestState: jest.fn(), serializeState: jest.fn(), reinitializeState: jest.fn(), diff --git a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 6355a7492e4bc..2de1162896ce5 100644 --- a/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/platform/plugins/shared/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; +import type { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; import type { ApplicationStart } from '@kbn/core/public'; import type { SerializableRecord } from '@kbn/utility-types'; import type { CollectConfigProps as CollectConfigPropsBase } from '@kbn/kibana-utils-plugin/public'; @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import type { DataViewsService } from '@kbn/data-views-plugin/public'; import { apiIsOfType } from '@kbn/presentation-publishing'; import type { LensApi } from '@kbn/lens-common-2'; -import { DOC_TYPE } from '../../common/constants'; +import { DISCOVER_DRILLDOWN_SUPPORTED_TRIGGERS, DOC_TYPE } from '../../common/constants'; import type { DiscoverAppLocator } from './open_in_discover_helpers'; export const getDiscoverHelpersAsync = async () => await import('../async_services'); @@ -62,7 +62,7 @@ export class OpenInDiscoverDrilldown public readonly euiIcon = 'discoverApp'; supportedTriggers(): OpenInDiscoverTrigger[] { - return [APPLY_FILTER_TRIGGER]; + return DISCOVER_DRILLDOWN_SUPPORTED_TRIGGERS as OpenInDiscoverTrigger[]; } private readonly ReactCollectConfig: React.FC = ({ config, onConfig }) => { diff --git a/x-pack/platform/plugins/shared/lens/server/drilldowns/register_discover_drilldown.ts b/x-pack/platform/plugins/shared/lens/server/drilldowns/register_discover_drilldown.ts new file mode 100644 index 0000000000000..c4165d001a0d4 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/server/drilldowns/register_discover_drilldown.ts @@ -0,0 +1,26 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { + DISCOVER_DRILLDOWN_SUPPORTED_TRIGGERS, + DISCOVER_DRILLDOWN_TYPE, +} from '../../common/constants'; + +export const discoverDrilldownSchema = schema.object({ + open_in_new_tab: schema.boolean({ + defaultValue: true, + }), +}); + +export function registerDiscoverDrilldown(embeddableSetup: EmbeddableSetup) { + embeddableSetup.registerDrilldown(DISCOVER_DRILLDOWN_TYPE, { + schema: discoverDrilldownSchema, + supportedTriggers: DISCOVER_DRILLDOWN_SUPPORTED_TRIGGERS, + }); +} diff --git a/x-pack/platform/plugins/shared/lens/server/plugin.tsx b/x-pack/platform/plugins/shared/lens/server/plugin.tsx index 6e9a77455bbb1..0d4d1b6402733 100644 --- a/x-pack/platform/plugins/shared/lens/server/plugin.tsx +++ b/x-pack/platform/plugins/shared/lens/server/plugin.tsx @@ -39,11 +39,10 @@ import { setupExpressions } from './expressions'; import { makeLensEmbeddableFactory } from './embeddable/make_lens_embeddable_factory'; import type { CustomVisualizationMigrations } from './migrations/types'; import { LensAppLocatorDefinition } from '../common/locator/locator'; -import { LENS_EMBEDDABLE_TYPE } from '../common/constants'; import { LensStorage } from './content_management'; import { registerLensAPIRoutes } from './api/routes'; import { fetchLensFeatureFlags } from '../common'; -import { getLensServerTransforms } from './transforms'; +import { registerLensEmbeddableTransforms } from './transforms'; export interface PluginSetupContract { taskManager?: TaskManagerSetupContract; @@ -132,10 +131,7 @@ export class LensServerPlugin builder.setEnabled(flags.apiFormat); // Need to wait for feature flags to be set before registering transforms - plugins.embeddable.registerTransforms( - LENS_EMBEDDABLE_TYPE, - getLensServerTransforms(builder, plugins.embeddable) - ); + registerLensEmbeddableTransforms(plugins.embeddable, builder); flags.apiFormat$.subscribe((value) => { builder.setEnabled(value); diff --git a/x-pack/platform/plugins/shared/lens/server/transforms.ts b/x-pack/platform/plugins/shared/lens/server/transforms.ts index 83b97c2544443..7b4a7eb3d5765 100644 --- a/x-pack/platform/plugins/shared/lens/server/transforms.ts +++ b/x-pack/platform/plugins/shared/lens/server/transforms.ts @@ -6,28 +6,40 @@ */ import { lensApiStateSchema, type LensConfigBuilder } from '@kbn/lens-embeddable-utils'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { LensSerializedAPIConfig } from '@kbn/lens-common-2'; import { schema } from '@kbn/config-schema'; -import { getLensTransforms } from '../common/transforms'; -import type { LensTransforms } from '../common/transforms/types'; +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; import { isByRefLensConfig } from '../common/transforms/utils'; import { lensItemDataSchemaV2 } from './content_management'; +import { LENS_EMBEDDABLE_TYPE } from '../common/constants'; +import { getTransformIn } from '../common/transforms/transform_in'; +import { getTransformOut } from '../common/transforms/transform_out'; -export const getLensServerTransforms = ( - builder: LensConfigBuilder, - { transformEnhancementsIn, transformEnhancementsOut }: EmbeddableSetup -): LensTransforms => { - return { - ...getLensTransforms({ - builder, - transformEnhancementsIn, - transformEnhancementsOut, +export function registerLensEmbeddableTransforms( + embeddableSetup: EmbeddableSetup, + builder: LensConfigBuilder +) { + embeddableSetup.registerTransforms(LENS_EMBEDDABLE_TYPE, { + getTransforms: (drilldownTransforms: DrilldownTransforms) => ({ + transformIn: getTransformIn(builder, drilldownTransforms.transformIn), + transformOut: getTransformOut(builder, drilldownTransforms.transformOut), }), - ...getExtraServerTransformProps(builder), - }; -}; + getSchema: () => { + return builder.isEnabled ? lensPanelSchema : undefined; + }, + throwOnUnmappedPanel: (config: LensSerializedAPIConfig) => { + if (isByRefLensConfig(config)) return; + + const chartType = builder.getType(config.attributes); + + if (builder.isEnabled && !builder.isSupported(chartType)) { + throw new Error(`Lens "${chartType}" chart type is not supported`); + } + }, + }); +} const legacyPanelAttributesSchema = lensItemDataSchemaV2.extends({ // Why are these added to the panel attributes? @@ -53,22 +65,3 @@ const lensByRefPanelSchema = schema.object( ); const lensPanelSchema = schema.oneOf([lensByValuePanelSchema, lensByRefPanelSchema]); - -function getExtraServerTransformProps( - builder: LensConfigBuilder -): Pick { - return { - getSchema: () => { - return builder.isEnabled ? lensPanelSchema : undefined; - }, - throwOnUnmappedPanel: (config: LensSerializedAPIConfig) => { - if (isByRefLensConfig(config)) return; - - const chartType = builder.getType(config.attributes); - - if (builder.isEnabled && !builder.isSupported(chartType)) { - throw new Error(`Lens "${chartType}" chart type is not supported`); - } - }, - }; -} diff --git a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_in.ts b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_in.ts index 0fef01c89cc95..9430adad9b49a 100644 --- a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_in.ts +++ b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_in.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { Reference } from '@kbn/content-management-utils'; import type { MapByReferenceState, MapByValueState, MapEmbeddableState } from '../types'; import type { StoredMapEmbeddableState } from './types'; @@ -14,24 +14,19 @@ import { transformMapAttributesIn } from '../../content_management/transform_map export const MAP_SAVED_OBJECT_REF_NAME = 'savedObjectRef'; -export function getTransformIn( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'] -) { +export function getTransformIn(transformDrilldownsIn: DrilldownTransforms['transformIn']) { function transformIn(state: MapEmbeddableState): { state: StoredMapEmbeddableState; references: Reference[]; } { - const enhancementsResult = state.enhancements - ? transformEnhancementsIn(state.enhancements) - : { state: undefined, references: [] }; + const { state: storedState, references: drilldownReferences } = transformDrilldownsIn(state); // by ref - if ((state as MapByReferenceState).savedObjectId) { - const { savedObjectId, ...rest } = state as MapByReferenceState; + if ((storedState as MapByReferenceState).savedObjectId) { + const { savedObjectId, ...rest } = storedState as MapByReferenceState; return { state: { ...rest, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), } as StoredMapEmbeddableState, references: [ { @@ -39,33 +34,31 @@ export function getTransformIn( type: MAP_SAVED_OBJECT_TYPE, id: savedObjectId!, }, - ...enhancementsResult.references, + ...drilldownReferences, ], }; } // by value - if ((state as MapByValueState).attributes) { + if ((storedState as MapByValueState).attributes) { const { attributes, references } = transformMapAttributesIn( - (state as MapByValueState).attributes + (storedState as MapByValueState).attributes ); return { state: { - ...state, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), + ...storedState, attributes, } as StoredMapEmbeddableState, - references: [...references, ...enhancementsResult.references], + references: [...references, ...drilldownReferences], }; } return { state: { - ...state, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), + ...storedState, } as StoredMapEmbeddableState, - references: enhancementsResult.references, + references: drilldownReferences, }; } return transformIn; diff --git a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_out.ts b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_out.ts index a0a7694c09158..d3aeb3d0a9c0b 100644 --- a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_out.ts +++ b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transform_out.ts @@ -7,25 +7,25 @@ import type { Reference } from '@kbn/content-management-utils/src/types'; import { transformTitlesOut } from '@kbn/presentation-publishing'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import { flow } from 'lodash'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { MAP_SAVED_OBJECT_TYPE } from '../../constants'; import { transformMapAttributesOut } from '../../content_management/transform_map_attributes_out'; import type { MapByValueState } from '../types'; import { MAP_SAVED_OBJECT_REF_NAME } from './get_transform_in'; import type { StoredMapEmbeddableState } from './types'; -export function getTransformOut( - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransformOut(transformDrilldownsOut: DrilldownTransforms['transformOut']) { function transformOut( storedState: StoredMapEmbeddableState, panelReferences?: Reference[], containerReferences?: Reference[] ) { - const state = transformTitlesOut(storedState); - const enhancementsState = state.enhancements - ? transformEnhancementsOut(state.enhancements, panelReferences ?? []) - : undefined; + const transformsFlow = flow( + transformTitlesOut, + (state: StoredMapEmbeddableState) => transformDrilldownsOut(state, panelReferences) + ); + const state = transformsFlow(storedState); // by ref const savedObjectRef = (panelReferences ?? []).find( @@ -34,7 +34,6 @@ export function getTransformOut( if (savedObjectRef) { return { ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), savedObjectId: savedObjectRef.id, }; } @@ -43,7 +42,6 @@ export function getTransformOut( if ((state as MapByValueState).attributes) { return { ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), attributes: transformMapAttributesOut( (state as MapByValueState).attributes, (targetName: string) => { @@ -56,10 +54,7 @@ export function getTransformOut( }; } - return { - ...state, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), - }; + return state; } return transformOut; } diff --git a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transforms.ts b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transforms.ts index 47e9bdda99306..1d70bc671fe75 100644 --- a/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transforms.ts +++ b/x-pack/platform/plugins/shared/maps/common/embeddable/transforms/get_transforms.ts @@ -5,16 +5,13 @@ * 2.0. */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { getTransformIn } from './get_transform_in'; import { getTransformOut } from './get_transform_out'; -export function getTransforms( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'], - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransforms(drilldownTransforms: DrilldownTransforms) { return { - transformIn: getTransformIn(transformEnhancementsIn), - transformOut: getTransformOut(transformEnhancementsOut), + transformIn: getTransformIn(drilldownTransforms.transformIn), + transformOut: getTransformOut(drilldownTransforms.transformOut), }; } diff --git a/x-pack/platform/plugins/shared/maps/public/react_embeddable/map_react_embeddable.tsx b/x-pack/platform/plugins/shared/maps/public/react_embeddable/map_react_embeddable.tsx index dcef8b5564c84..bd1cc3f2d87e2 100644 --- a/x-pack/platform/plugins/shared/maps/public/react_embeddable/map_react_embeddable.tsx +++ b/x-pack/platform/plugins/shared/maps/public/react_embeddable/map_react_embeddable.tsx @@ -66,7 +66,7 @@ export const mapEmbeddableFactory: EmbeddableFactory const controlledBy = getControlledBy(uuid); const titleManager = initializeTitleManager(state); const timeRangeManager = initializeTimeRangeManager(state); - const dynamicActionsManager = getEmbeddableEnhanced()?.initializeEmbeddableDynamicActions( + const dynamicActionsManager = await getEmbeddableEnhanced()?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -132,7 +132,7 @@ export const mapEmbeddableFactory: EmbeddableFactory getComparators: () => { return { ...crossPanelActionsComparators, - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { drilldowns: 'skip', enhancements: 'skip' }), ...reduxSyncComparators, ...titleComparators, ...timeRangeComparators, diff --git a/x-pack/platform/plugins/shared/maps/public/react_embeddable/setup_map_embeddable.ts b/x-pack/platform/plugins/shared/maps/public/react_embeddable/setup_map_embeddable.ts index 505a55d83d410..e0d5842d3686e 100644 --- a/x-pack/platform/plugins/shared/maps/public/react_embeddable/setup_map_embeddable.ts +++ b/x-pack/platform/plugins/shared/maps/public/react_embeddable/setup_map_embeddable.ts @@ -7,6 +7,7 @@ import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; import { untilPluginStartServicesReady } from '../kibana_services'; import type { MapEmbeddableState } from '../../common'; @@ -43,8 +44,11 @@ export function setupMapEmbeddable(embeddableSetup: EmbeddableSetup) { getIconForSavedObject: () => APP_ICON, }); - embeddableSetup.registerLegacyURLTransform(MAP_SAVED_OBJECT_TYPE, async () => { - const { getTransformOut } = await import('./embeddable_module'); - return getTransformOut(embeddableSetup.transformEnhancementsOut); - }); + embeddableSetup.registerLegacyURLTransform( + MAP_SAVED_OBJECT_TYPE, + async (transformDrilldownsOut: DrilldownTransforms['transformOut']) => { + const { getTransformOut } = await import('./embeddable_module'); + return getTransformOut(transformDrilldownsOut); + } + ); } diff --git a/x-pack/platform/plugins/shared/maps/server/plugin.ts b/x-pack/platform/plugins/shared/maps/server/plugin.ts index 8f72d06ff58e6..61947b1cdab67 100644 --- a/x-pack/platform/plugins/shared/maps/server/plugin.ts +++ b/x-pack/platform/plugins/shared/maps/server/plugin.ts @@ -273,13 +273,9 @@ export class MapsPlugin implements Plugin { setupEmbeddable(plugins.embeddable, getFilterMigrations, getDataViewMigrations); - plugins.embeddable.registerTransforms( - MAP_SAVED_OBJECT_TYPE, - getTransforms( - plugins.embeddable.transformEnhancementsIn, - plugins.embeddable.transformEnhancementsOut - ) - ); + plugins.embeddable.registerTransforms(MAP_SAVED_OBJECT_TYPE, { + getTransforms, + }); return { config: config$, diff --git a/x-pack/solutions/observability/plugins/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx b/x-pack/solutions/observability/plugins/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx index b2160e7bbeeda..de6591e01b0cb 100644 --- a/x-pack/solutions/observability/plugins/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx @@ -67,7 +67,7 @@ export const getOverviewEmbeddableFactory = ({ const deps = { ...coreStart, ...pluginsStart }; const state = initialState; - const dynamicActionsManager = deps.embeddableEnhanced?.initializeEmbeddableDynamicActions( + const dynamicActionsManager = await deps.embeddableEnhanced?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -106,7 +106,7 @@ export const getOverviewEmbeddableFactory = ({ remoteName: 'referenceEquality', overviewMode: 'referenceEquality', ...titleComparators, - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { drilldowns: 'skip', enhancements: 'skip' }), }), onReset: (lastSaved) => { dynamicActionsManager?.reinitializeState(lastSaved ?? {}); diff --git a/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_in.ts b/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_in.ts index 0ebc53e909c7c..4d6d79d34cabb 100644 --- a/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_in.ts +++ b/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_in.ts @@ -5,29 +5,16 @@ * 2.0. */ -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; import type { Reference } from '@kbn/content-management-utils'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { OverviewStatsEmbeddableState } from './types'; -export function getTransformIn( - transformEnhancementsIn: EmbeddableSetup['transformEnhancementsIn'] -) { +export function getTransformIn(transformDrilldownsIn: DrilldownTransforms['transformIn']) { function transformIn(state: OverviewStatsEmbeddableState): { state: OverviewStatsEmbeddableState; references: Reference[]; } { - const { enhancements, ...rest } = state; - const enhancementsResult = enhancements - ? transformEnhancementsIn(enhancements) - : { state: undefined, references: [] }; - - return { - state: { - ...rest, - ...(enhancementsResult.state ? { enhancements: enhancementsResult.state } : {}), - } as OverviewStatsEmbeddableState, - references: enhancementsResult.references, - }; + return transformDrilldownsIn(state); } return transformIn; } diff --git a/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_out.ts b/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_out.ts index 1271ea043045f..417ce12855503 100644 --- a/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_out.ts +++ b/x-pack/solutions/observability/plugins/synthetics/common/embeddables/stats_overview/get_transform_out.ts @@ -7,23 +7,17 @@ import type { Reference } from '@kbn/content-management-utils/src/types'; import { transformTitlesOut } from '@kbn/presentation-publishing'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; +import { flow } from 'lodash'; import type { OverviewStatsEmbeddableState } from './types'; -export function getTransformOut( - transformEnhancementsOut: EmbeddableSetup['transformEnhancementsOut'] -) { +export function getTransformOut(transformDrilldownsOut: DrilldownTransforms['transformOut']) { function transformOut(storedState: OverviewStatsEmbeddableState, references?: Reference[]) { - const state = transformTitlesOut(storedState); - const { enhancements, ...rest } = state; - const enhancementsState = enhancements - ? transformEnhancementsOut(enhancements, references ?? []) - : undefined; - - return { - ...rest, - ...(enhancementsState ? { enhancements: enhancementsState } : {}), - }; + const transformsFlow = flow( + transformTitlesOut, + (state: OverviewStatsEmbeddableState) => transformDrilldownsOut(state, references) + ); + return transformsFlow(storedState); } return transformOut; } diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/register_embeddables.ts b/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/register_embeddables.ts index b349ab9134acb..1e62b175b84bc 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/register_embeddables.ts +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/register_embeddables.ts @@ -7,6 +7,7 @@ import type { CoreSetup } from '@kbn/core-lifecycle-browser'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import type { ClientPluginsSetup, ClientPluginsStart } from '../../plugin'; import { SYNTHETICS_MONITORS_EMBEDDABLE } from './constants'; import { SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE } from '../../../common/embeddables/stats_overview/constants'; @@ -26,11 +27,11 @@ export const registerSyntheticsEmbeddables = ( ); pluginsSetup.embeddable.registerLegacyURLTransform( SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE, - async () => { + async (transformDrilldownsOut: DrilldownTransforms['transformOut']) => { const { getTransformOut } = await import( '../../../common/embeddables/stats_overview/get_transform_out' ); - return getTransformOut(pluginsSetup.embeddable.transformEnhancementsOut); + return getTransformOut(transformDrilldownsOut); } ); diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/stats_overview/stats_overview_embeddable_factory.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/stats_overview/stats_overview_embeddable_factory.tsx index be1420b1b3bc5..f9888aafd31ad 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/stats_overview/stats_overview_embeddable_factory.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/embeddables/stats_overview/stats_overview_embeddable_factory.tsx @@ -68,7 +68,7 @@ export const getStatsOverviewEmbeddableFactory = ( const filters$ = new BehaviorSubject(initialState.filters); const { embeddableEnhanced } = pluginStart; - const dynamicActionsManager = embeddableEnhanced?.initializeEmbeddableDynamicActions( + const dynamicActionsManager = await embeddableEnhanced?.initializeEmbeddableDynamicActions( uuid, () => titleManager.api.title$.getValue(), initialState @@ -95,7 +95,7 @@ export const getStatsOverviewEmbeddableFactory = ( getComparators: () => ({ ...titleComparators, filters: 'referenceEquality', - ...(dynamicActionsManager?.comparators ?? { enhancements: 'skip' }), + ...(dynamicActionsManager?.comparators ?? { drilldowns: 'skip', enhancements: 'skip' }), }), defaultState: { filters: DEFAULT_FILTERS, diff --git a/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts b/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts index 3e8c37ebffe3b..bb5bb4d4d2a5d 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts @@ -16,6 +16,7 @@ import type { import { SavedObjectsClient } from '@kbn/core/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { Dataset } from '@kbn/rule-registry-plugin/server'; +import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common'; import { SyncGlobalParamsPrivateLocationsTask } from './tasks/sync_global_params_task'; import type { SyntheticsPluginsSetupDependencies, @@ -111,8 +112,10 @@ export class Plugin implements PluginType { this.syncGlobalParamsTask.registerTaskDefinition(plugins.taskManager); plugins.embeddable.registerTransforms(SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE, { - transformIn: getTransformIn(plugins.embeddable.transformEnhancementsIn), - transformOut: getTransformOut(plugins.embeddable.transformEnhancementsOut), + getTransforms: (drilldownTransforms: DrilldownTransforms) => ({ + transformIn: getTransformIn(drilldownTransforms.transformIn), + transformOut: getTransformOut(drilldownTransforms.transformOut), + }), }); return {};