From 704035413d7aea64570af5c92729f68668fea8a8 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 14 Oct 2025 18:57:47 -0500 Subject: [PATCH 01/15] [Lens] move references to server --- .../lens/common/embeddable_factory/index.ts | 58 ----------- .../shared/lens/common/references/index.ts | 53 ++++++++++ .../common/transforms/config_builder_stub.ts | 38 -------- .../shared/lens/common/transforms/index.ts | 11 ++- .../lens/common/transforms/transform_in.ts | 29 ++++++ .../lens/common/transforms/transform_out.ts | 67 +++++++++++++ .../shared/lens/common/transforms/types.ts | 40 ++++++++ .../shared/lens/common/transforms/utils.ts | 13 +++ .../shared/lens/public/app_plugin/mounter.tsx | 5 +- .../save_modal_container_helpers.ts | 5 +- .../lens/public/lens_attribute_service.ts | 26 +---- .../lens/public/mocks/services_mock.tsx | 11 --- .../shared/lens/public/persistence/utils.ts | 8 +- .../plugins/shared/lens/public/plugin.ts | 5 + .../public/react_embeddable/helper.test.ts | 57 ----------- .../initialize_dashboard_services.ts | 14 ++- .../react_embeddable/lens_embeddable.tsx | 1 - .../lens_custom_renderer_component.tsx | 52 +++++----- .../public/react_embeddable/renderer/types.ts | 37 +++++++ .../lens/public/react_embeddable/types.ts | 53 +++++----- .../shared/lens/server/api/routes/utils.ts | 97 +++++++++++++------ .../api/routes/visualizations/create.ts | 44 ++++----- .../server/api/routes/visualizations/get.ts | 14 +-- .../routes/visualizations/schema/common.ts | 40 ++++++++ .../routes/visualizations/schema/create.ts | 39 ++++---- .../api/routes/visualizations/schema/get.ts | 4 +- .../routes/visualizations/schema/search.ts | 3 +- .../routes/visualizations/schema/update.ts | 48 +++++---- .../server/api/routes/visualizations/types.ts | 13 ++- .../api/routes/visualizations/update.ts | 29 +++--- .../content_management/v0/schema/common.ts | 4 + .../content_management/v1/schema/common.ts | 77 +++------------ .../content_management/v1/schema/create.ts | 2 +- .../server/content_management/v1/types.ts | 10 -- .../make_lens_embeddable_factory.ts | 7 +- .../lens/server/embeddable/references.ts | 27 ++++++ .../shared/lens/server/embeddable/types.ts | 22 +++++ .../plugins/shared/lens/server/plugin.tsx | 16 ++- 38 files changed, 609 insertions(+), 470 deletions(-) delete mode 100644 x-pack/platform/plugins/shared/lens/common/embeddable_factory/index.ts create mode 100644 x-pack/platform/plugins/shared/lens/common/references/index.ts delete mode 100644 x-pack/platform/plugins/shared/lens/common/transforms/config_builder_stub.ts create mode 100644 x-pack/platform/plugins/shared/lens/common/transforms/transform_in.ts create mode 100644 x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts create mode 100644 x-pack/platform/plugins/shared/lens/common/transforms/types.ts create mode 100644 x-pack/platform/plugins/shared/lens/common/transforms/utils.ts create mode 100644 x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts create mode 100644 x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/common.ts create mode 100644 x-pack/platform/plugins/shared/lens/server/embeddable/references.ts create mode 100644 x-pack/platform/plugins/shared/lens/server/embeddable/types.ts diff --git a/x-pack/platform/plugins/shared/lens/common/embeddable_factory/index.ts b/x-pack/platform/plugins/shared/lens/common/embeddable_factory/index.ts deleted file mode 100644 index 18cc02c08fe69..0000000000000 --- a/x-pack/platform/plugins/shared/lens/common/embeddable_factory/index.ts +++ /dev/null @@ -1,58 +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 { cloneDeep } from 'lodash'; -import type { SerializableRecord } from '@kbn/utility-types'; -import type { Reference } from '@kbn/content-management-utils'; -import type { - EmbeddableRegistryDefinition, - EmbeddableStateWithType, -} from '@kbn/embeddable-plugin/common'; -import type { LensRuntimeState } from '../../public'; - -export type LensEmbeddablePersistableState = EmbeddableStateWithType & { - attributes: SerializableRecord; -}; - -export const inject: NonNullable = ( - state, - references -): EmbeddableStateWithType => { - const typedState = cloneDeep(state) as unknown as LensRuntimeState; - - if (typedState.savedObjectId) { - return typedState as unknown as EmbeddableStateWithType; - } - - // match references based on name, so only references associated with this lens panel are injected. - const matchedReferences: Reference[] = []; - - if (Array.isArray(typedState.attributes.references)) { - typedState.attributes.references.forEach((serializableRef) => { - const internalReference = serializableRef; - const matchedReference = references.find( - (reference) => reference.name === internalReference.name - ); - if (matchedReference) matchedReferences.push(matchedReference); - }); - } - - typedState.attributes.references = matchedReferences; - - return typedState as unknown as EmbeddableStateWithType; -}; - -export const extract: NonNullable = (state) => { - let references: Reference[] = []; - const typedState = state as unknown as LensRuntimeState; - - if ('attributes' in typedState && typedState.attributes !== undefined) { - references = typedState.attributes.references; - } - - return { state, references }; -}; diff --git a/x-pack/platform/plugins/shared/lens/common/references/index.ts b/x-pack/platform/plugins/shared/lens/common/references/index.ts new file mode 100644 index 0000000000000..c8a7c9d90795d --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/common/references/index.ts @@ -0,0 +1,53 @@ +/* + * 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 { cloneDeep } from 'lodash'; + +import type { Reference } from '@kbn/content-management-utils'; + +import type { LensSerializedState } from '../../public'; + +export const injectLensReferences = ( + state: LensSerializedState, + references: Reference[] = [] +): LensSerializedState => { + const clonedState = cloneDeep(state); + + if (clonedState.savedObjectId || !clonedState.attributes) { + return clonedState; + } + + // match references based on name, so only references associated with this lens panel are injected. + const matchedReferences: Reference[] = []; + + if (Array.isArray(clonedState.attributes.references)) { + clonedState.attributes.references.forEach((serializableRef) => { + const internalReference = serializableRef; + const matchedReference = references.find( + (reference) => reference.name === internalReference.name + ); + if (matchedReference) matchedReferences.push(matchedReference); + }); + } + + // Keep refs on attributes for now, need to clean this up. + clonedState.attributes.references = matchedReferences; + + return clonedState; +}; + +export const extractLensReferences = ( + state: LensSerializedState +): { + state: LensSerializedState; + references: Reference[]; +} => { + return { + state, + references: state.references ?? state.attributes?.references ?? [], + }; +}; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/config_builder_stub.ts b/x-pack/platform/plugins/shared/lens/common/transforms/config_builder_stub.ts deleted file mode 100644 index e0cfd5f89ea5b..0000000000000 --- a/x-pack/platform/plugins/shared/lens/common/transforms/config_builder_stub.ts +++ /dev/null @@ -1,38 +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 { LensAPIConfig, LensItem } from '../../server/content_management'; - -export function isNewApiFormat(config: unknown): config is LensAPIConfig { - return (config as LensAPIConfig)?.state?.isNewApiFormat; -} - -export const ConfigBuilderStub = { - /** - * @returns Lens item - */ - in(config: LensAPIConfig): LensItem { - const { isNewApiFormat: _, ...cleanedState } = config.state; - return { - ...config, - state: cleanedState, - }; - }, - - /** - * @returns Lens API config - */ - out(item: LensItem): LensAPIConfig { - return { - ...item, - state: { - ...item.state, - isNewApiFormat: true, - }, - }; - }, -}; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts index f294802fed3ed..3138e8b761ba2 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts @@ -5,4 +5,13 @@ * 2.0. */ -export { ConfigBuilderStub } from './config_builder_stub'; +import type { LensTransforms } from './types'; +import { getTransformIn } from './transform_in'; +import { getTransformOut } from './transform_out'; + +export function getLensTransforms(): LensTransforms { + return { + transformIn: getTransformIn(), + transformOut: getTransformOut(), + }; +} 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 new file mode 100644 index 0000000000000..f4c18e8d25a95 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/common/transforms/transform_in.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 { extractLensReferences } from '../references'; +import type { + LensByRefTransformInResult, + LensByValueTransformInResult, + LensTransformIn, +} from './types'; +import { isByRefLensState } from './utils'; + +/** + * Transform from Lens API format to Lens Serialized State + */ +export const getTransformIn = (): LensTransformIn => { + return function transformIn(state) { + if (isByRefLensState(state)) { + return { + state, + } satisfies LensByRefTransformInResult; + } + + return extractLensReferences(state) 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 new file mode 100644 index 0000000000000..e2f7e1b6e26fb --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/common/transforms/transform_out.ts @@ -0,0 +1,67 @@ +/* + * 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 { LensByValueSerializedState } from '../../public/react_embeddable/types'; +import { LENS_ITEM_VERSION_V1, transformToV1LensItemAttributes } from '../content_management/v1'; +import { injectLensReferences } from '../references'; +import type { + LensByRefTransformOutResult, + LensByValueTransformOutResult, + LensTransformOut, +} from './types'; +import { isByRefLensState } from './utils'; + +/** + * Transform from Lens Serialized State to Lens API format + */ +export const getTransformOut = (): LensTransformOut => { + return function transformOut(state, references) { + if (isByRefLensState(state)) { + return { + ...state, + } satisfies LensByRefTransformOutResult; + } + + const migratedAttributes = migrateAttributes(state.attributes); + const injectedState = injectLensReferences( + { + ...state, + attributes: migratedAttributes, + }, + references + ); + + return injectedState satisfies LensByValueTransformOutResult; + }; +}; + +/** + * Handles transforming old lens SO in dashboard to v1 Lens SO + */ +function migrateAttributes(attributes: LensByValueSerializedState['attributes']) { + if (!attributes) { + throw new Error('Why are attributes undefined?'); + } + + const { visualizationType } = attributes; + + if (!visualizationType) { + throw new Error('Missing visualizationType'); + } + + const version = attributes.version ?? 0; + + let newAttributes = { ...attributes }; + if (version < LENS_ITEM_VERSION_V1) { + newAttributes = { + ...newAttributes, + ...transformToV1LensItemAttributes({ ...attributes, visualizationType }), + }; + } + + return newAttributes; +} diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/types.ts b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts new file mode 100644 index 0000000000000..49a37127ece8a --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EmbeddableTransforms } from '@kbn/embeddable-plugin/common'; + +import type { + LensSerializedState, + LensByRefSerializedState, + LensByValueSerializedState, +} from '../../public/react_embeddable/types'; + +export type LensTransforms = Required< + EmbeddableTransforms +>; + +/** + * Transform from Lens API format to Lens Serialized State + */ +export type LensTransformIn = LensTransforms['transformIn']; + +/** + * Transform from to Lens Serialized State to Lens API format + */ +export type LensTransformOut = LensTransforms['transformOut']; + +type LensByRefTransforms = Required< + EmbeddableTransforms +>; +export type LensByRefTransformInResult = ReturnType; +export type LensByRefTransformOutResult = ReturnType; + +type LensByValueTransforms = Required< + EmbeddableTransforms +>; +export type LensByValueTransformInResult = ReturnType; +export type LensByValueTransformOutResult = ReturnType; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts new file mode 100644 index 0000000000000..81cb48cef32ac --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts @@ -0,0 +1,13 @@ +/* + * 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 { LensSerializedState } from '../../public'; +import type { LensByRefSerializedState } from '../../public/react_embeddable/types'; + +export function isByRefLensState(state: LensSerializedState): state is LensByRefSerializedState { + return !state.attributes; +} diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/mounter.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/mounter.tsx index 0802f75b5231d..4a927322e6a5a 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/mounter.tsx +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/mounter.tsx @@ -19,7 +19,6 @@ import { Storage, withNotifyOnErrors, } from '@kbn/kibana-utils-plugin/public'; -import type { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import { ACTION_VISUALIZE_LENS_FIELD } from '@kbn/ui-actions-plugin/public'; import { ACTION_CONVERT_TO_LENS } from '@kbn/visualizations-plugin/public'; @@ -37,7 +36,7 @@ import type { } from '../types'; import { addHelpMenuToAppChrome } from '../help_menu_util'; import type { LensPluginStartDependencies } from '../plugin'; -import { extract } from '../../common/embeddable_factory'; +import { extractLensReferences } from '../../common/references'; import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID } from '../../common/constants'; import type { LensAttributesService } from '../lens_attribute_service'; import type { LensAppServices, RedirectToOriginProps, HistoryLocationState } from './types'; @@ -216,7 +215,7 @@ export async function mountApp( } if (stateTransfer && props?.state) { const { state: rawState, isCopied } = props; - const { references } = extract(rawState as unknown as EmbeddableStateWithType); + const { references } = extractLensReferences(rawState); stateTransfer.navigateToWithEmbeddablePackage(mergedOriginatingApp, { path: embeddableEditorIncomingState?.originatingPath, state: { diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container_helpers.ts b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container_helpers.ts index 5e3766056d760..790ad6adc05c2 100644 --- a/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container_helpers.ts +++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/save_modal_container_helpers.ts @@ -15,10 +15,9 @@ import { DEFAULT_AUTO_APPLY_SELECTIONS, CONTROLS_GROUP_TYPE, } from '@kbn/controls-constants'; -import type { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import type { LensAppServices } from './types'; import { LENS_EMBEDDABLE_TYPE } from '../../common/constants'; -import { extract } from '../../common/embeddable_factory'; +import { extractLensReferences } from '../../common/references'; import type { LensSerializedState } from '../react_embeddable/types'; /** @@ -62,7 +61,7 @@ export const redirectToDashboard = ({ stateTransfer: LensAppServices['stateTransfer']; controlsState?: ControlPanelsState; }) => { - const { references } = extract(rawState as unknown as EmbeddableStateWithType); + const { references } = extractLensReferences(rawState); const appId = originatingApp || 'dashboards'; diff --git a/x-pack/platform/plugins/shared/lens/public/lens_attribute_service.ts b/x-pack/platform/plugins/shared/lens/public/lens_attribute_service.ts index 44273a858928a..f79890b145a40 100644 --- a/x-pack/platform/plugins/shared/lens/public/lens_attribute_service.ts +++ b/x-pack/platform/plugins/shared/lens/public/lens_attribute_service.ts @@ -10,12 +10,10 @@ import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import type { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; import { noop } from 'lodash'; import type { HttpStart } from '@kbn/core/public'; -import type { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; -import { extract, inject } from '../common/embeddable_factory'; import { LensDocumentService } from './persistence'; import { DOC_TYPE } from '../common/constants'; import type { SharingSavedObjectProps } from './types'; -import type { LensRuntimeState, LensSavedObjectAttributes } from './react_embeddable/types'; +import type { LensSavedObjectAttributes } from './react_embeddable/types'; type CheckDuplicateTitleProps = OnSaveProps & { id?: string; @@ -36,14 +34,6 @@ export interface LensAttributesService { savedObjectId?: string ) => Promise; checkForDuplicateTitle: (props: CheckDuplicateTitleProps) => Promise<{ isDuplicate: boolean }>; - injectReferences: ( - runtimeState: LensRuntimeState, - references: Reference[] | undefined - ) => LensRuntimeState; - extractReferences: (runtimeState: LensRuntimeState) => { - rawState: LensRuntimeState; - references: Reference[]; - }; } export const savedObjectToEmbeddableAttributes = ( @@ -119,19 +109,5 @@ export function getLensAttributeService(http: HttpStart): LensAttributesService ), }; }, - // Make sure to inject references from the container down to the runtime state - // this ensure migrations/copy to spaces works correctly - injectReferences: (runtimeState, references) => { - return inject( - runtimeState as unknown as EmbeddableStateWithType, - references ?? runtimeState.attributes.references - ) as unknown as LensRuntimeState; - }, - // Make sure to move the internal references into the parent references - // so migrations/move to spaces can work properly - extractReferences: (runtimeState) => { - const { state, references } = extract(runtimeState as unknown as EmbeddableStateWithType); - return { rawState: state as unknown as LensRuntimeState, references }; - }, }; } diff --git a/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx b/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx index 4def5c3cf6881..89fbaa17dadf7 100644 --- a/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx +++ b/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx @@ -65,17 +65,6 @@ export function makeAttributeService(doc: LensDocument): jest.Mocked ({ - ..._runtimeState, - attributes: { - ..._runtimeState.attributes, - references: references?.length ? references : _runtimeState.attributes.references, - }, - })), - extractReferences: jest.fn((_runtimeState) => ({ - rawState: _runtimeState, - references: _runtimeState.attributes.references || [], - })), }; return attributeServiceMock; diff --git a/x-pack/platform/plugins/shared/lens/public/persistence/utils.ts b/x-pack/platform/plugins/shared/lens/public/persistence/utils.ts index baf82d5253cfc..22037f8fde5e1 100644 --- a/x-pack/platform/plugins/shared/lens/public/persistence/utils.ts +++ b/x-pack/platform/plugins/shared/lens/public/persistence/utils.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { LensItem, LensItemMeta, LensSavedObject } from '../../server/content_management'; +import type { LensSavedObject } from '../../server/content_management'; +import type { LensItemResponse } from './lens_client'; /** * Converts Lens Response Item to Lens Saved Object @@ -15,10 +16,7 @@ import type { LensItem, LensItemMeta, LensSavedObject } from '../../server/conte export function getLensSOFromResponse({ item: { id, references, ...attributes }, meta: { type, createdAt, updatedAt, createdBy, updatedBy, managed, originId }, -}: { - item: LensItem; - meta: LensItemMeta; -}): LensSavedObject { +}: LensItemResponse): LensSavedObject { return { id, references, diff --git a/x-pack/platform/plugins/shared/lens/public/plugin.ts b/x-pack/platform/plugins/shared/lens/public/plugin.ts index 1307faf54cf29..711af3ee9f2f5 100644 --- a/x-pack/platform/plugins/shared/lens/public/plugin.ts +++ b/x-pack/platform/plugins/shared/lens/public/plugin.ts @@ -390,6 +390,11 @@ export class LensPlugin { return createLensEmbeddableFactory(deps); }); + embeddable.registerTransforms(LENS_EMBEDDABLE_TYPE, async () => { + const { getLensTransforms } = await import('../common/transforms'); + return getLensTransforms(); + }); + // Let Dashboard know about the Lens panel type embeddable.registerAddFromLibraryType({ onAdd: async (container, savedObject) => { diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.test.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.test.ts index e4c0e1ab9173b..b133129d1a3d6 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.test.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.test.ts @@ -61,63 +61,6 @@ describe('Embeddable helpers', () => { // check the visualizationType set to null for empty state expect(runtimeState.attributes.visualizationType).toBeNull(); }); - - describe('injected references should overwrite inner ones', () => { - // There are 3 possible scenarios here for reference injections: - // * default space for a by-value - // * default space for a by-ref with a "lens" panel reference type - // * other space for a by-value with new ref ids - - it('should inject correctly serialized references into runtime state for a by value in the default space', async () => { - const services = getServices(); - const mockedReferences = [ - { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, - ]; - const runtimeState = await deserializeState( - services, - { - attributes: defaultDoc, - }, - mockedReferences - ); - expect(services.attributeService.injectReferences).toHaveBeenCalled(); - expect(runtimeState.attributes.references).toEqual(mockedReferences); - }); - - it('should inject correctly serialized references into runtime state for a by ref in the default space', async () => { - const services = getServices(); - const mockedReferences = [ - { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, - ]; - const runtimeState = await deserializeState( - services, - { - savedObjectId: '123', - }, - mockedReferences - ); - expect(services.attributeService.injectReferences).not.toHaveBeenCalled(); - // Note the original references should be kept - expect(runtimeState.attributes.references).toEqual(defaultDoc.references); - }); - - it('should inject correctly serialized references into runtime state for a by value in another space', async () => { - const services = getServices(); - const mockedReferences = [ - { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, - ]; - const runtimeState = await deserializeState( - services, - { - attributes: defaultDoc, - }, - mockedReferences - ); - expect(services.attributeService.injectReferences).toHaveBeenCalled(); - // note: in this case the references are swapped - expect(runtimeState.attributes.references).toEqual(mockedReferences); - }); - }); }); describe('getStructuredDatasourceStates', () => { diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts index 0a23838f0c830..66963a26dcf9f 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts @@ -17,7 +17,7 @@ import { titleComparators } from '@kbn/presentation-publishing'; import { apiIsPresentationContainer, apiPublishesSettings } from '@kbn/presentation-containers'; import type { Observable } from 'rxjs'; import { BehaviorSubject, map, merge } from 'rxjs'; -import { isTextBasedLanguage } from '../helper'; +import { isTextBasedLanguage, transformOutputState } from '../helper'; import type { LensComponentProps, LensPanelProps, @@ -131,12 +131,18 @@ export function initializeDashboardServices( canUnlinkFromLibrary: async () => Boolean(getLatestState().savedObjectId), getSerializedStateByReference: (newId: string) => { const currentState = getLatestState(); - currentState.savedObjectId = newId; - return attributeService.extractReferences(currentState); + return { + rawState: { + ...currentState, + savedObjectId: newId, + }, + }; }, getSerializedStateByValue: () => { const { savedObjectId, ...byValueRuntimeState } = getLatestState(); - return attributeService.extractReferences(byValueRuntimeState); + return { + rawState: transformOutputState(byValueRuntimeState), + }; }, }, anyStateChange$: merge( 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 14f354d3918f4..3ec0f5dc5ebbd 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 @@ -122,7 +122,6 @@ export const createLensEmbeddableFactory = ( const integrationsConfig = initializeIntegrations( getLatestState, dynamicActionsManager?.serializeState, - services, internalApi ); const actionsConfig = initializeActionApi( diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx index cd5d205300fad..e4918841daec8 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx @@ -12,7 +12,8 @@ import { BehaviorSubject } from 'rxjs'; import type { PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; import type { LensApi, LensRendererProps, LensSerializedState } from '../types'; import { LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; -import { createEmptyLensState } from '../helper'; +import { createEmptyLensState, transformOutputState } from '../helper'; +import type { LensParentApi } from './types'; // This little utility uses the same pattern of the useSearchApi hook: // create the Subject once and then update its value on change @@ -142,30 +143,31 @@ export function LensRenderer({ type={LENS_EMBEDDABLE_TYPE} maybeId={id} - // TODO type this ParentApi, all these are untyped and some unused - getParentApi={() => ({ - // forward the Lens components to the embeddable - ...props, - // forward the unified search context - ...searchApi, - searchSessionId$, - disabledActionIds$, - setDisabledActionIds: (ids: string[] | undefined) => disabledActionIds$.next(ids), - viewMode$, - // pass the sync* settings with the unified settings interface - settings, - // make sure to provide the initial state (useful for the comparison check) - getSerializedStateForChild: () => ({ rawState: initialStateRef.current, references: [] }), - // update the runtime state on changes - getRuntimeStateForChild: () => ({ - ...initialStateRef.current, - attributes: props.attributes, - }), - forceDSL, - esqlVariables$, - hideTitle$, - reload$, // trigger a reload (replacement for deprepcated searchSessionId) - })} + getParentApi={() => + ({ + // forward the Lens components to the embeddable + ...props, + // forward the unified search context + ...searchApi, + searchSessionId$, + disabledActionIds$, + setDisabledActionIds: (ids: string[] | undefined) => disabledActionIds$.next(ids), + viewMode$, + // pass the sync* settings with the unified settings interface + settings, + // make sure to provide the initial state (useful for the comparison check) + getSerializedStateForChild: () => { + const transformedState = transformOutputState(initialStateRef.current); + return { + rawState: transformedState, + }; + }, + forceDSL, + esqlVariables$, + hideTitle$, + reload$, // trigger a reload (replacement for deprecated searchSessionId) + } satisfies LensParentApi) + } onApiAvailable={setLensApi} hidePanelChrome={!showPanelChrome} panelProps={panelProps} diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts new file mode 100644 index 0000000000000..45018d1cd6ace --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts @@ -0,0 +1,37 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; + +import type { ESQLControlVariable } from '@kbn/esql-types'; +import type { ViewMode, useSearchApi } from '@kbn/presentation-publishing'; +import type { HasSerializedChildState } from '@kbn/presentation-containers'; + +import type { LensRuntimeState, LensSerializedState } from '../types'; + +type SearchApi = ReturnType; + +interface GeneralLensApi { + searchSessionId$: BehaviorSubject; + disabledActionIds$: BehaviorSubject; + setDisabledActionIds: (ids: string[] | undefined) => void; + viewMode$: BehaviorSubject; + settings: { + syncColors$: BehaviorSubject; + syncCursor$: BehaviorSubject; + syncTooltips$: BehaviorSubject; + }; + forceDSL?: boolean; + esqlVariables$: BehaviorSubject; + hideTitle$: BehaviorSubject; + reload$: BehaviorSubject; +} + +export type LensParentApi = SearchApi & + LensRuntimeState & + GeneralLensApi & + HasSerializedChildState; diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts index dc0fe9b8c4e52..d666f1bc53e0f 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts @@ -97,7 +97,7 @@ import type { MetricVisualizationState } from '../visualizations/metric/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface interface LensApiProps {} -export type LensSavedObjectAttributes = Omit; +export type LensSavedObjectAttributes = Simplify>; /** * This visualization context can have a different attributes than the @@ -151,9 +151,9 @@ export interface PreventableEvent { preventDefault(): void; } -interface LensByValue { - // by-value - attributes?: Simplify; +interface LensByValueBase { + savedObjectId?: string; + attributes?: LensSavedObjectAttributes; } export interface LensOverrides { @@ -175,10 +175,9 @@ export interface LensOverrides { /** * Lens embeddable props broken down by type */ - -export interface LensByReference { - // by-reference - savedObjectId?: string; +interface LensByReferenceBase { + savedObjectId?: string; // really should be never + attributes?: never; } interface ContentManagementProps { @@ -186,9 +185,9 @@ interface ContentManagementProps { managed?: boolean; } -export type LensPropsVariants = (LensByValue & LensByReference) & { +interface LensWithReferences { references?: Reference[]; -}; +} export interface ViewInDiscoverCallbacks extends LensApiProps { canViewUnderlyingData$: PublishingSubject; @@ -276,18 +275,9 @@ interface LensRequestHandlersProps { abortController?: AbortController; } -/** - * Compose together all the props and make them inspectable via Simplify - * - * The LensSerializedState is the state stored for a dashboard panel - * that contains: - * * Lens document state - * * Panel settings - * * other props from the embeddable - */ -export type LensSerializedState = Simplify< - LensPropsVariants & - LensOverrides & +type LensSerializedSharedState = Simplify< + LensOverrides & + LensWithReferences & LensUnifiedSearchContext & LensPanelProps & SerializedTitles & @@ -295,6 +285,19 @@ export type LensSerializedState = Simplify< Partial & { isNewPanel?: boolean } >; +export type LensByValueSerializedState = Simplify; +export type LensByRefSerializedState = Simplify; + +/** + * Combined properties of serialized state stored on dashboard panel + * + * Includes: + * - Lens document state (for by-value) + * - Panel settings + * - other props from the embeddable + */ +export type LensSerializedState = LensByRefSerializedState | LensByValueSerializedState; + /** * Custom props exposed on the Lens exported component */ @@ -352,9 +355,9 @@ export type LensRendererProps = LensRendererPrivateProps; /** * The LensRuntimeState is the state stored for a dashboard panel * that contains: - * * Lens document state - * * Panel settings - * * other props from the embeddable + * - Lens document state + * - Panel settings + * - other props from the embeddable */ export type LensRuntimeState = Simplify< Omit & { diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/utils.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/utils.ts index ba060788f4945..44aa6909cad53 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/utils.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/utils.ts @@ -5,41 +5,80 @@ * 2.0. */ -import type { LensResponseItem, LensSavedObject } from '../../content_management'; -import { ConfigBuilderStub } from '../../../common/transforms'; +import type { LensSavedObject, LensUpdateIn } from '../../content_management'; +import type { + LensCreateRequestBody, + LensItemMeta, + LensResponseItem, + LensUpdateRequestBody, +} from './types'; + +/** + * Converts Lens request data to Lens Config + */ +export function getLensRequestConfig( + request: LensCreateRequestBody | LensUpdateRequestBody +): LensUpdateIn['data'] & LensUpdateIn['options'] { + const { visualizationType, ...attributes } = request; + + if (!visualizationType) { + throw new Error('Missing visualizationType'); + } + + return { + ...attributes, + // TODO: fix these type issues + visualizationType, + title: attributes.title ?? '', + description: attributes.description ?? undefined, + } satisfies LensUpdateIn['data'] & LensUpdateIn['options']; +} + +/** + * Used to extend the meta of the response item. Needed in Lens GET request. + */ +export type ExtendedLensResponseItem = {}> = Omit< + LensResponseItem, + 'meta' +> & { + meta: LensResponseItem['meta'] & M; +}; /** * Converts Lens Saved Object to Lens Response Item */ -export function getLensResponseItem({ - // Data params - id, - references, - attributes, - - // Meta params - type, - createdAt, - updatedAt, - createdBy, - updatedBy, - managed, - originId, -}: LensSavedObject): LensResponseItem { +export function getLensResponseItem>( + item: LensSavedObject, + extraMeta: M = {} as M +): ExtendedLensResponseItem { + const { id, references, attributes } = item; + const meta = getLensResponseItemMeta(item, extraMeta); + return { - data: ConfigBuilderStub.out({ - ...attributes, - id, + id, + data: { references, - }), - meta: { - type, - createdAt, - updatedAt, - createdBy, - updatedBy, - managed, - originId, + ...attributes, }, + meta, + } satisfies LensResponseItem; +} + +/** + * Converts Lens Saved Object to Lens Response Item + */ +function getLensResponseItemMeta>( + { type, createdAt, updatedAt, createdBy, updatedBy, managed, originId }: LensSavedObject, + extraMeta: M = {} as M +): LensItemMeta & M { + return { + type, + createdAt, + updatedAt, + createdBy, + updatedBy, + managed, + originId, + ...extraMeta, }; } diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/create.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/create.ts index cc25dcc3ef6f4..a13e001030914 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/create.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/create.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { omit } from 'lodash'; import { boomify, isBoom } from '@hapi/boom'; -import type { TypeOf } from '@kbn/config-schema'; - import { LENS_VIS_API_PATH, LENS_API_VERSION, @@ -17,18 +14,21 @@ import { LENS_CONTENT_TYPE, } from '../../../../common/constants'; import type { LensCreateIn, LensSavedObject } from '../../../content_management'; -import type { RegisterAPIRouteFn } from '../../types'; -import { ConfigBuilderStub } from '../../../../common/transforms'; -import { lensCreateRequestBodySchema, lensCreateResponseBodySchema } from './schema'; -import { getLensResponseItem } from '../utils'; -import { isNewApiFormat } from '../../../../common/transforms/config_builder_stub'; +import type { LensCreateResponseBody, RegisterAPIRouteFn } from '../../types'; +import { + lensCreateRequestBodySchema, + lensCreateRequestParamsSchema, + lensCreateRequestQuerySchema, + lensCreateResponseBodySchema, +} from './schema'; +import { getLensRequestConfig, getLensResponseItem } from '../utils'; export const registerLensVisualizationsCreateAPIRoute: RegisterAPIRouteFn = ( router, { contentManagement } ) => { const createRoute = router.post({ - path: LENS_VIS_API_PATH, + path: `${LENS_VIS_API_PATH}/{id?}`, access: LENS_API_ACCESS, enableQueryVersion: true, summary: 'Create Lens visualization', @@ -52,6 +52,8 @@ export const registerLensVisualizationsCreateAPIRoute: RegisterAPIRouteFn = ( version: LENS_API_VERSION, validate: { request: { + query: lensCreateRequestQuerySchema, + params: lensCreateRequestParamsSchema, body: lensCreateRequestBodySchema, }, response: { @@ -75,8 +77,8 @@ export const registerLensVisualizationsCreateAPIRoute: RegisterAPIRouteFn = ( }, }, async (ctx, req, res) => { - const requestBodyData = req.body.data; - if (!requestBodyData.visualizationType) { + const requestBodyData = req.body; + if ('state' in requestBodyData && !requestBodyData.visualizationType) { throw new Error('visualizationType is required'); } @@ -85,29 +87,19 @@ export const registerLensVisualizationsCreateAPIRoute: RegisterAPIRouteFn = ( .getForRequest({ request: req, requestHandlerContext: ctx }) .for(LENS_CONTENT_TYPE); - const { references, ...lensItem } = isNewApiFormat(requestBodyData) - ? // TODO: Find a better way to conditionally omit id - omit(ConfigBuilderStub.in(requestBodyData), 'id') - : // For now we need to be able to create old SO, this may be moved to the config builder - ({ - ...requestBodyData, - // fix type mismatches, null -> undefined - description: requestBodyData.description ?? undefined, - visualizationType: requestBodyData.visualizationType, - } satisfies LensCreateIn['data']); - try { // Note: these types are to enforce loose param typings of client methods - const data: LensCreateIn['data'] = lensItem; - const options: LensCreateIn['options'] = { ...req.body.options, references }; + const { references, ...data } = getLensRequestConfig(req.body); + const options: LensCreateIn['options'] = { ...req.query, references, id: req.params.id }; const { result } = await client.create(data, options); if (result.item.error) { throw result.item.error; } - return res.created>({ - body: getLensResponseItem(result.item), + const responseItem = getLensResponseItem(result.item); + return res.created({ + body: responseItem, }); } catch (error) { if (isBoom(error) && error.output.statusCode === 403) { diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/get.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/get.ts index 5ea4aee4dbc38..1fa8b23a144ac 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/get.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/get.ts @@ -16,7 +16,7 @@ import { LENS_CONTENT_TYPE, } from '../../../../common/constants'; import type { LensSavedObject } from '../../../content_management'; -import type { RegisterAPIRouteFn } from '../../types'; +import type { CMItemResultMeta, RegisterAPIRouteFn } from '../../types'; import { lensGetRequestParamsSchema, lensGetResponseBodySchema } from './schema'; import { getLensResponseItem } from '../utils'; @@ -87,15 +87,11 @@ export const registerLensVisualizationsGetAPIRoute: RegisterAPIRouteFn = ( throw result.item.error; } - const body = getLensResponseItem(result.item); + const resultMeta: CMItemResultMeta = result.meta; + const responseItem = getLensResponseItem(result.item, resultMeta); + return res.ok>({ - body: { - ...body, - meta: { - ...body.meta, - ...result.meta, - }, - }, + body: responseItem, }); } catch (error) { if (isBoom(error)) { diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/common.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/common.ts new file mode 100644 index 0000000000000..e0e5dba76a3a1 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/common.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { lensItemDataSchema, lensSavedObjectSchema } from '../../../../content_management'; +import { pickFromObjectSchema } from '../../../../utils'; + +/** + * The Lens item meta returned from the server + */ +export const lensItemMetaSchema = schema.object( + { + ...pickFromObjectSchema(lensSavedObjectSchema.getPropSchemas(), [ + 'type', + 'createdAt', + 'updatedAt', + 'createdBy', + 'updatedBy', + 'originId', + 'managed', + ]), + }, + { unknowns: 'forbid' } +); + +/** + * The Lens response item returned from the server + */ +export const lensResponseItemSchema = schema.object( + { + id: lensSavedObjectSchema.getPropSchemas().id, + data: lensItemDataSchema, + meta: lensItemMetaSchema, + }, + { unknowns: 'forbid' } +); diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/create.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/create.ts index d6974bf129ef0..ec8fa1231c4c2 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/create.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/create.ts @@ -8,36 +8,31 @@ import { schema } from '@kbn/config-schema'; import { - lensResponseItemSchema, - lensAPIConfigSchema, lensCMCreateOptionsSchema, + lensItemDataSchema, + lensItemSchema, } from '../../../../content_management'; -import { lensItemSchemaV0 } from '../../../../content_management/v0'; +import { lensItemDataSchemaV0 } from '../../../../content_management/v0'; import { pickFromObjectSchema } from '../../../../utils'; +import { lensResponseItemSchema } from './common'; -const apiConfigData = lensAPIConfigSchema.extends({ - id: undefined, -}); - -const v0ConfigData = lensItemSchemaV0.extends({ - id: undefined, -}); +export const lensCreateRequestParamsSchema = schema.object( + { + id: schema.maybe(lensItemSchema.getPropSchemas().id), + }, + { unknowns: 'forbid' } +); -export const lensCreateRequestBodySchema = schema.object( +export const lensCreateRequestQuerySchema = schema.object( { - data: schema.oneOf([ - apiConfigData, - v0ConfigData, // Temporarily permit passing old v0 SO attributes on create - ]), - // TODO should these options be here or in params? - options: schema.object( - { - ...pickFromObjectSchema(lensCMCreateOptionsSchema.getPropSchemas(), ['overwrite']), - }, - { unknowns: 'forbid' } - ), + ...pickFromObjectSchema(lensCMCreateOptionsSchema.getPropSchemas(), ['overwrite']), }, { unknowns: 'forbid' } ); +export const lensCreateRequestBodySchema = schema.oneOf([ + lensItemDataSchema, + lensItemDataSchemaV0, // Temporarily permit passing old v0 SO attributes on create +]); + export const lensCreateResponseBodySchema = lensResponseItemSchema; diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/get.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/get.ts index a65da3195fc47..476a98094c94e 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/get.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/get.ts @@ -7,7 +7,8 @@ import { schema } from '@kbn/config-schema'; -import { lensCMGetResultSchema, lensResponseItemSchema } from '../../../../content_management'; +import { lensCMGetResultSchema } from '../../../../content_management'; +import { lensResponseItemSchema } from './common'; export const lensGetRequestParamsSchema = schema.object( { @@ -22,6 +23,7 @@ export const lensGetRequestParamsSchema = schema.object( export const lensGetResponseBodySchema = schema.object( { + id: lensResponseItemSchema.getPropSchemas().id, data: lensResponseItemSchema.getPropSchemas().data, meta: schema.object( { diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/search.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/search.ts index 4db82d8646cdd..ab9d8e7f8ddec 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/search.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/search.ts @@ -8,8 +8,9 @@ import { schema } from '@kbn/config-schema'; import { searchOptionsSchemas } from '@kbn/content-management-utils'; -import { lensCMSearchOptionsSchema, lensResponseItemSchema } from '../../../../content_management'; +import { lensCMSearchOptionsSchema } from '../../../../content_management'; import { pickFromObjectSchema } from '../../../../utils'; +import { lensResponseItemSchema } from './common'; // TODO cleanup and align search options types with client side options // TODO align defaults with cm and other schema definitions (i.e. searchOptionsSchemas) diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/update.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/update.ts index 2758e5049d36c..25ed3a091fb96 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/update.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/schema/update.ts @@ -5,16 +5,13 @@ * 2.0. */ +import { omit } from 'lodash'; + import { schema } from '@kbn/config-schema'; -import { omit } from 'lodash'; -import { - lensResponseItemSchema, - lensAPIAttributesSchema, - lensAPIConfigSchema, - lensCMUpdateOptionsSchema, -} from '../../../../content_management'; -import { pickFromObjectSchema } from '../../../../utils'; +import { lensCMUpdateOptionsSchema, lensItemDataSchema } from '../../../../content_management'; +import { lensItemDataSchemaV0 } from '../../../../content_management/v0'; +import { lensResponseItemSchema } from './common'; export const lensUpdateRequestParamsSchema = schema.object( { @@ -27,27 +24,28 @@ export const lensUpdateRequestParamsSchema = schema.object( { unknowns: 'forbid' } ); -export const lensUpdateRequestBodySchema = schema.object( +export const lensUpdateRequestQuerySchema = schema.object( { - data: schema.object( - { - ...lensAPIAttributesSchema.getPropSchemas(), - // omit id on create options - ...pickFromObjectSchema(lensAPIConfigSchema.getPropSchemas(), ['references']), - }, - { unknowns: 'forbid' } - ), - // TODO should these options be here? - options: schema.object( + ...omit(lensCMUpdateOptionsSchema.getPropSchemas(), ['references']), + }, + { unknowns: 'forbid' } +); + +export const lensUpdateRequestBodySchema = schema.oneOf([ + lensItemDataSchema, + lensItemDataSchemaV0, // Temporarily permit passing old v0 SO attributes on create +]); + +export const lensUpdateResponseBodySchema = schema.object( + { + id: lensResponseItemSchema.getPropSchemas().id, + data: lensResponseItemSchema.getPropSchemas().data, + meta: schema.object( { - ...omit(lensCMUpdateOptionsSchema.getPropSchemas(), ['references']), + ...lensResponseItemSchema.getPropSchemas().meta.getPropSchemas(), }, { unknowns: 'forbid' } ), }, - { - unknowns: 'forbid', - } + { unknowns: 'forbid' } ); - -export const lensUpdateResponseBodySchema = lensResponseItemSchema; diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/types.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/types.ts index 31b1e825c3275..99cef968846ff 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/types.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/types.ts @@ -5,11 +5,14 @@ * 2.0. */ +import type { Optional } from 'utility-types'; + import type { TypeOf } from '@kbn/config-schema'; -import type { Optional } from 'utility-types'; +import type { lensCMGetResultSchema } from '../../../content_management'; import type { lensCreateRequestBodySchema, + lensCreateRequestQuerySchema, lensCreateResponseBodySchema, lensDeleteRequestParamsSchema, lensGetRequestParamsSchema, @@ -18,13 +21,21 @@ import type { lensSearchResponseBodySchema, lensUpdateRequestBodySchema, lensUpdateRequestParamsSchema, + lensUpdateRequestQuerySchema, lensUpdateResponseBodySchema, } from './schema'; +import type { lensItemMetaSchema, lensResponseItemSchema } from './schema/common'; + +export type LensItemMeta = TypeOf; +export type LensResponseItem = TypeOf; +export type CMItemResultMeta = TypeOf['meta']; +export type LensCreateRequestQuery = TypeOf; export type LensCreateRequestBody = TypeOf; export type LensCreateResponseBody = TypeOf; export type LensUpdateRequestParams = TypeOf; +export type LensUpdateRequestQuery = TypeOf; export type LensUpdateRequestBody = TypeOf; export type LensUpdateResponseBody = TypeOf; diff --git a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/update.ts b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/update.ts index d5bab1a71bf37..e12f91e07af26 100644 --- a/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/update.ts +++ b/x-pack/platform/plugins/shared/lens/server/api/routes/visualizations/update.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { omit } from 'lodash'; import { boomify, isBoom } from '@hapi/boom'; -import type { TypeOf } from '@kbn/config-schema'; import { LENS_VIS_API_PATH, @@ -16,14 +14,14 @@ import { LENS_CONTENT_TYPE, } from '../../../../common/constants'; import type { LensUpdateIn, LensSavedObject } from '../../../content_management'; -import type { RegisterAPIRouteFn } from '../../types'; -import { ConfigBuilderStub } from '../../../../common/transforms'; +import type { LensUpdateResponseBody, RegisterAPIRouteFn } from '../../types'; import { lensUpdateRequestBodySchema, lensUpdateRequestParamsSchema, + lensUpdateRequestQuerySchema, lensUpdateResponseBodySchema, } from './schema'; -import { getLensResponseItem } from '../utils'; +import { getLensRequestConfig, getLensResponseItem } from '../utils'; export const registerLensVisualizationsUpdateAPIRoute: RegisterAPIRouteFn = ( router, @@ -56,6 +54,7 @@ export const registerLensVisualizationsUpdateAPIRoute: RegisterAPIRouteFn = ( request: { params: lensUpdateRequestParamsSchema, body: lensUpdateRequestBodySchema, + query: lensUpdateRequestQuerySchema, }, response: { 200: { @@ -81,7 +80,7 @@ export const registerLensVisualizationsUpdateAPIRoute: RegisterAPIRouteFn = ( }, }, async (ctx, req, res) => { - const requestBodyData = req.body.data; + const requestBodyData = req.body; if (!requestBodyData.visualizationType) { throw new Error('visualizationType is required'); } @@ -91,26 +90,20 @@ export const registerLensVisualizationsUpdateAPIRoute: RegisterAPIRouteFn = ( .getForRequest({ request: req, requestHandlerContext: ctx }) .for(LENS_CONTENT_TYPE); - const { references, ...lensItem } = omit( - ConfigBuilderStub.in({ - id: '', // TODO: Find a better way to conditionally omit id - ...req.body.data, - }), - 'id' - ); + // Note: these types are to enforce loose param typings of client methods + const { references, ...data } = getLensRequestConfig(req.body); + const options: LensUpdateIn['options'] = { ...req.query, references }; try { - // Note: these types are to enforce loose param typings of client methods - const data: LensUpdateIn['data'] = lensItem; - const options: LensUpdateIn['options'] = { references }; const { result } = await client.update(req.params.id, data, options); if (result.item.error) { throw result.item.error; } - return res.ok>({ - body: getLensResponseItem(result.item), + const responseItem = getLensResponseItem(result.item); + return res.ok({ + body: responseItem, }); } catch (error) { if (isBoom(error)) { diff --git a/x-pack/platform/plugins/shared/lens/server/content_management/v0/schema/common.ts b/x-pack/platform/plugins/shared/lens/server/content_management/v0/schema/common.ts index 5df9f7232d1d7..874fd5781fd45 100644 --- a/x-pack/platform/plugins/shared/lens/server/content_management/v0/schema/common.ts +++ b/x-pack/platform/plugins/shared/lens/server/content_management/v0/schema/common.ts @@ -52,3 +52,7 @@ export const lensItemSchemaV0 = schema.object( }, { unknowns: 'forbid' } ); + +export const lensItemDataSchemaV0 = lensItemSchemaV0.extends({ + id: undefined, +}); diff --git a/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/common.ts b/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/common.ts index 28fb9099e3ee2..1b96f0f5dcc97 100644 --- a/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/common.ts +++ b/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/common.ts @@ -23,21 +23,6 @@ export const lensItemAttributesSchema = schema.object( { unknowns: 'forbid' } ); -export const lensAPIStateSchema = schema.object( - { - isNewApiFormat: schema.literal(true), // pin this to validate CB transformations - }, - { unknowns: 'allow' } -); - -export const lensAPIAttributesSchema = schema.object( - { - ...lensItemAttributesSchema.getPropSchemas(), - state: lensAPIStateSchema, - }, - { unknowns: 'forbid' } -); - /** * The underlying SO type used to store Lens state in Content Management. * @@ -45,68 +30,30 @@ export const lensAPIAttributesSchema = schema.object( */ export const lensSavedObjectSchema = savedObjectSchema(lensItemAttributesSchema); -/** - * The common SO type used for mSearch items. - */ -export const lensCommonSavedObjectSchema = savedObjectSchema( - schema.object( - { - ...pickFromObjectSchema(lensItemAttributesSchema.getPropSchemas(), ['title', 'description']), - }, - { unknowns: 'forbid' } - ) -); - /** * The Lens item data returned from the server */ export const lensItemSchema = schema.object( { ...pickFromObjectSchema(lensSavedObjectSchema.getPropSchemas(), ['id', 'references']), - // Spread attributes at root ...lensSavedObjectSchema.getPropSchemas().attributes.getPropSchemas(), }, { unknowns: 'forbid' } ); /** - * The Lens item data returned from the server - */ -export const lensAPIConfigSchema = schema.object( - { - // TODO flatten this with new CB shape - ...pickFromObjectSchema(lensSavedObjectSchema.getPropSchemas(), ['id', 'references']), - // Spread attributes at root - ...lensAPIAttributesSchema.getPropSchemas(), - }, - { unknowns: 'forbid' } -); - -/** - * The Lens item meta returned from the server + * The common SO type used for mSearch items. */ -export const lensItemMetaSchema = schema.object( - { - ...pickFromObjectSchema(lensSavedObjectSchema.getPropSchemas(), [ - 'type', - 'createdAt', - 'updatedAt', - 'createdBy', - 'updatedBy', - 'originId', // maybe?? - 'managed', - ]), - }, - { unknowns: 'forbid' } +export const lensCommonSavedObjectSchema = savedObjectSchema( + schema.object( + { + ...pickFromObjectSchema(lensItemAttributesSchema.getPropSchemas(), ['title', 'description']), + }, + { unknowns: 'forbid' } + ) ); -/** - * The Lens response item returned from the server - */ -export const lensResponseItemSchema = schema.object( - { - data: lensAPIConfigSchema, - meta: lensItemMetaSchema, - }, - { unknowns: 'forbid' } -); +// TODO: cleanup data for update, should we forbid or just ignore body.id on update? +export const lensItemDataSchema = lensItemSchema.extends({ + id: undefined, +}); diff --git a/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/create.ts b/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/create.ts index 512edba1b5c52..6f0f604e7c04e 100644 --- a/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/create.ts +++ b/x-pack/platform/plugins/shared/lens/server/content_management/v1/schema/create.ts @@ -14,7 +14,7 @@ import { pickFromObjectSchema } from '../../../utils'; export const lensCMCreateOptionsSchema = schema.object( { - ...pickFromObjectSchema(createOptionsSchemas, ['overwrite', 'references']), + ...pickFromObjectSchema(createOptionsSchemas, ['id', 'overwrite', 'references']), }, { unknowns: 'forbid' } ); diff --git a/x-pack/platform/plugins/shared/lens/server/content_management/v1/types.ts b/x-pack/platform/plugins/shared/lens/server/content_management/v1/types.ts index 90889c8b9e0ab..a32eb023b51ac 100644 --- a/x-pack/platform/plugins/shared/lens/server/content_management/v1/types.ts +++ b/x-pack/platform/plugins/shared/lens/server/content_management/v1/types.ts @@ -29,21 +29,11 @@ import type { lensCMUpdateOptionsSchema, lensCMSearchOptionsSchema, lensItemSchema, - lensItemMetaSchema, - lensResponseItemSchema, - lensAPIAttributesSchema, - lensAPIConfigSchema, } from './schema'; import type { LENS_CONTENT_TYPE } from '../../../common/constants'; export type LensAttributes = TypeOf; -export type LensAPIAttributes = TypeOf; - export type LensItem = TypeOf; -export type LensItemMeta = TypeOf; - -export type LensAPIConfig = TypeOf; -export type LensResponseItem = TypeOf; export type LensCreateOptions = TypeOf; export type LensUpdateOptions = TypeOf; diff --git a/x-pack/platform/plugins/shared/lens/server/embeddable/make_lens_embeddable_factory.ts b/x-pack/platform/plugins/shared/lens/server/embeddable/make_lens_embeddable_factory.ts index e6f0ca054b23d..3a82689d2abfd 100644 --- a/x-pack/platform/plugins/shared/lens/server/embeddable/make_lens_embeddable_factory.ts +++ b/x-pack/platform/plugins/shared/lens/server/embeddable/make_lens_embeddable_factory.ts @@ -5,11 +5,12 @@ * 2.0. */ -import type { EmbeddableRegistryDefinition } from '@kbn/embeddable-plugin/server'; import type { SerializableRecord } from '@kbn/utility-types'; import type { SavedObject } from '@kbn/core-saved-objects-server'; import type { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { mergeMigrationFunctionMaps } from '@kbn/kibana-utils-plugin/common'; + +import { inject, extract } from './references'; import { DOC_TYPE } from '../../common/constants'; import { commonEnhanceTableRowHeight, @@ -49,7 +50,7 @@ import type { VisStatePre830, XYVisState850, } from '../migrations/types'; -import { extract, inject } from '../../common/embeddable_factory'; +import type { LensEmbeddableRegistryDefinition } from './types'; export const makeLensEmbeddableFactory = ( @@ -57,7 +58,7 @@ export const makeLensEmbeddableFactory = getDataViewMigrations: () => MigrateFunctionsObject, customVisualizationMigrations: CustomVisualizationMigrations ) => - (): EmbeddableRegistryDefinition => { + (): LensEmbeddableRegistryDefinition => { return { id: DOC_TYPE, migrations: () => diff --git a/x-pack/platform/plugins/shared/lens/server/embeddable/references.ts b/x-pack/platform/plugins/shared/lens/server/embeddable/references.ts new file mode 100644 index 0000000000000..68fc93fbc7abc --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/server/embeddable/references.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 { extractLensReferences, injectLensReferences } from '../../common/references'; +import type { LensEmbeddableRegistryDefinition } from './types'; + +export const inject: LensEmbeddableRegistryDefinition['inject'] = (state, references) => { + return { + ...state, + ...injectLensReferences(state, references), + }; +}; + +export const extract: LensEmbeddableRegistryDefinition['extract'] = (state) => { + const { state: newState, references } = extractLensReferences(state); + return { + state: { + ...state, + ...newState, + }, + references, + }; +}; diff --git a/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts b/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts new file mode 100644 index 0000000000000..0c0ee9bc0a215 --- /dev/null +++ b/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts @@ -0,0 +1,22 @@ +/* + * 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 { + EmbeddableRegistryDefinition, + EmbeddableStateWithType, +} from '@kbn/embeddable-plugin/server'; + +import type { LensRuntimeState } from '../../public'; +import type { DOC_TYPE } from '../../common/constants'; + +type LensEmbeddableStateWithType = EmbeddableStateWithType & + LensRuntimeState & { + type: typeof DOC_TYPE; + }; + +export type LensEmbeddableRegistryDefinition = + EmbeddableRegistryDefinition; diff --git a/x-pack/platform/plugins/shared/lens/server/plugin.tsx b/x-pack/platform/plugins/shared/lens/server/plugin.tsx index 479b6fa473150..dc983ab9ef48f 100644 --- a/x-pack/platform/plugins/shared/lens/server/plugin.tsx +++ b/x-pack/platform/plugins/shared/lens/server/plugin.tsx @@ -26,7 +26,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; +import type { EmbeddableRegistryDefinition, EmbeddableSetup } from '@kbn/embeddable-plugin/server'; import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; import type { SharePluginSetup } from '@kbn/share-plugin/server'; import { setupSavedObjects } from './saved_objects'; @@ -34,9 +34,14 @@ 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_CONTENT_TYPE, LENS_ITEM_LATEST_VERSION } from '../common/constants'; +import { + LENS_CONTENT_TYPE, + LENS_EMBEDDABLE_TYPE, + LENS_ITEM_LATEST_VERSION, +} from '../common/constants'; import { LensStorage } from './content_management'; import { registerLensAPIRoutes } from './api/routes'; +import { getLensTransforms } from '../common/transforms'; export interface PluginSetupContract { taskManager?: TaskManagerSetupContract; @@ -105,7 +110,12 @@ export class LensServerPlugin DataViewPersistableStateService.getAllMigrations.bind(DataViewPersistableStateService), this.customVisualizationMigrations ); - plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory()); + + plugins.embeddable.registerEmbeddableFactory( + lensEmbeddableFactory() as unknown as EmbeddableRegistryDefinition + ); + + plugins.embeddable.registerTransforms(LENS_EMBEDDABLE_TYPE, getLensTransforms()); registerLensAPIRoutes({ http: core.http, From c1f0633e008ec8584eb63e36444afbff69109712 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 21 Oct 2025 14:54:43 -0700 Subject: [PATCH 02/15] chore: cleanup transforms, handle dynamic actions, stub out api config transforms --- .../shared/lens/common/references/index.ts | 3 +- .../shared/lens/common/transforms/index.ts | 15 ++- .../lens/common/transforms/transform_in.ts | 32 ++++- .../lens/common/transforms/transform_out.ts | 22 +++- .../shared/lens/common/transforms/utils.ts | 11 ++ .../lens/public/persistence/lens_client.ts | 111 ++++++++++-------- .../plugins/shared/lens/public/plugin.ts | 7 +- .../lens/public/react_embeddable/helper.ts | 41 ++++--- .../initializers/initialize_integrations.ts | 78 ++++-------- .../react_embeddable/lens_embeddable.tsx | 18 +-- .../lens/public/react_embeddable/types.ts | 3 + .../plugins/shared/lens/server/plugin.tsx | 8 +- 12 files changed, 198 insertions(+), 151 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/common/references/index.ts b/x-pack/platform/plugins/shared/lens/common/references/index.ts index c8a7c9d90795d..0b490fc71374e 100644 --- a/x-pack/platform/plugins/shared/lens/common/references/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/references/index.ts @@ -34,7 +34,6 @@ export const injectLensReferences = ( }); } - // Keep refs on attributes for now, need to clean this up. clonedState.attributes.references = matchedReferences; return clonedState; @@ -48,6 +47,6 @@ export const extractLensReferences = ( } => { return { state, - references: state.references ?? state.attributes?.references ?? [], + references: state.attributes?.references ?? state.references ?? [], }; }; diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts index 3138e8b761ba2..14e6c71efd652 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts @@ -5,13 +5,22 @@ * 2.0. */ +import { schema } from '@kbn/config-schema'; +import type { EnhancementsRegistry } from '@kbn/embeddable-plugin/common/enhancements/registry'; + import type { LensTransforms } from './types'; import { getTransformIn } from './transform_in'; import { getTransformOut } from './transform_out'; -export function getLensTransforms(): LensTransforms { +export interface LensTransformDependencies { + transformEnhancementsIn?: EnhancementsRegistry['transformIn']; + transformEnhancementsOut?: EnhancementsRegistry['transformOut']; +} + +export function getLensTransforms(deps: LensTransformDependencies): LensTransforms { return { - transformIn: getTransformIn(), - transformOut: getTransformOut(), + transformIn: getTransformIn(deps), + transformOut: getTransformOut(deps), + schema: schema.any(), }; } 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 f4c18e8d25a95..921cf24661d09 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 @@ -5,25 +5,49 @@ * 2.0. */ +import type { LensTransformDependencies } from '.'; +import { DOC_TYPE } from '../constants'; import { extractLensReferences } from '../references'; import type { LensByRefTransformInResult, LensByValueTransformInResult, LensTransformIn, } from './types'; -import { isByRefLensState } from './utils'; +import { LENS_SAVED_OBJECT_REF_NAME, isByRefLensState } from './utils'; /** * Transform from Lens API format to Lens Serialized State */ -export const getTransformIn = (): LensTransformIn => { +export const getTransformIn = ({ + transformEnhancementsIn, +}: LensTransformDependencies): LensTransformIn => { return function transformIn(state) { + const { enhancementsState: enhancements = null, enhancementsReferences = [] } = + state.enhancements ? transformEnhancementsIn?.(state.enhancements) ?? {} : {}; + const enhancementsState = enhancements ? { enhancements } : {}; + if (isByRefLensState(state)) { + const { savedObjectId: id, ...rest } = state; return { - state, + state: rest, + ...enhancementsState, + references: [ + { + name: LENS_SAVED_OBJECT_REF_NAME, + type: DOC_TYPE, + id: id!, + }, + ...enhancementsReferences, + ], } satisfies LensByRefTransformInResult; } - return extractLensReferences(state) satisfies LensByValueTransformInResult; + const { state: lensState, references: lensReferences } = extractLensReferences(state); + + return { + state: lensState, + ...enhancementsState, + references: [...lensReferences, ...enhancementsReferences], + } 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 e2f7e1b6e26fb..ed0662122aa17 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,6 +5,8 @@ * 2.0. */ +import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; +import type { LensTransformDependencies } from '.'; import type { LensByValueSerializedState } from '../../public/react_embeddable/types'; import { LENS_ITEM_VERSION_V1, transformToV1LensItemAttributes } from '../content_management/v1'; import { injectLensReferences } from '../references'; @@ -13,16 +15,29 @@ import type { LensByValueTransformOutResult, LensTransformOut, } from './types'; -import { isByRefLensState } from './utils'; +import { findLensReference, isByRefLensState } from './utils'; /** * Transform from Lens Serialized State to Lens API format */ -export const getTransformOut = (): LensTransformOut => { +export const getTransformOut = ({ + transformEnhancementsOut, +}: LensTransformDependencies): LensTransformOut => { return function transformOut(state, references) { - if (isByRefLensState(state)) { + const enhancements = state.enhancements + ? transformEnhancementsOut?.(state.enhancements, references ?? []) + : undefined; + const enhancementsState = ( + enhancements ? { enhancements } : {} + ) as DynamicActionsSerializedState; + + const savedObjectRef = findLensReference(references); + + if (savedObjectRef && isByRefLensState(state)) { return { ...state, + ...enhancementsState, + savedObjectId: savedObjectRef.id, } satisfies LensByRefTransformOutResult; } @@ -30,6 +45,7 @@ export const getTransformOut = (): LensTransformOut => { const injectedState = injectLensReferences( { ...state, + ...enhancementsState, attributes: migratedAttributes, }, references diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts index 81cb48cef32ac..bbadad13dcaf3 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts @@ -5,8 +5,19 @@ * 2.0. */ +import type { Reference } from '@kbn/content-management-utils'; + import type { LensSerializedState } from '../../public'; import type { LensByRefSerializedState } from '../../public/react_embeddable/types'; +import { DOC_TYPE } from '../constants'; + +export const LENS_SAVED_OBJECT_REF_NAME = 'savedObjectRef'; + +export function findLensReference(references?: Reference[]) { + return references + ? references.find((ref) => ref.type === DOC_TYPE && ref.name === LENS_SAVED_OBJECT_REF_NAME) + : undefined; +} export function isByRefLensState(state: LensSerializedState): state is LensByRefSerializedState { return !state.attributes; diff --git a/x-pack/platform/plugins/shared/lens/public/persistence/lens_client.ts b/x-pack/platform/plugins/shared/lens/public/persistence/lens_client.ts index 5adc1bb47949f..b921ec0df261c 100644 --- a/x-pack/platform/plugins/shared/lens/public/persistence/lens_client.ts +++ b/x-pack/platform/plugins/shared/lens/public/persistence/lens_client.ts @@ -8,10 +8,8 @@ import type { HttpStart } from '@kbn/core/public'; import type { Reference } from '@kbn/content-management-utils'; -import { omit } from 'lodash'; import { LENS_API_VERSION, LENS_VIS_API_PATH } from '../../common/constants'; import type { LensAttributes, LensItem } from '../../server/content_management'; -import { ConfigBuilderStub } from '../../common/transforms'; import { type LensGetResponseBody, type LensCreateRequestBody, @@ -21,8 +19,18 @@ import { type LensSearchRequestQuery, type LensSearchResponseBody, } from '../../server'; +import type { + LensCreateRequestQuery, + LensItemMeta, + LensUpdateRequestQuery, +} from '../../server/api/routes/visualizations/types'; import type { LensSavedObjectAttributes } from '../react_embeddable/types'; +export interface LensItemResponse = {}> { + item: LensItem; + meta: LensItemMeta & M; +} + /** * This type is to allow `visualizationType` to be `null` in the public context. * @@ -34,50 +42,56 @@ export type LooseLensAttributes = Omit & export class LensClient { constructor(private http: HttpStart) {} - async get(id: string) { - const { data, meta } = await this.http.get(`${LENS_VIS_API_PATH}/${id}`, { + async get(id: string): Promise> { + const { + data, + meta, + id: responseId, + } = await this.http.get(`${LENS_VIS_API_PATH}/${id}`, { version: LENS_API_VERSION, }); return { - item: ConfigBuilderStub.in(data), - meta, // TODO: see if we still need this meta data + item: { + ...data, + id: responseId, + }, + meta, }; } async create( { description, visualizationType, state, title, version }: LooseLensAttributes, references: Reference[], - options: LensCreateRequestBody['options'] = {} - ) { + options: LensCreateRequestQuery = {} + ): Promise { if (visualizationType === null) { throw new Error('Missing visualization type'); } const body: LensCreateRequestBody = { - // TODO: Find a better way to conditionally omit id - data: omit( - ConfigBuilderStub.out({ - id: '', - description, - visualizationType, - state, - title, - version, - references, - }), - 'id' - ), - options, + description, + visualizationType, + state, + title, + version, + references, }; - const { data, meta } = await this.http.post(LENS_VIS_API_PATH, { - body: JSON.stringify(body), - version: LENS_API_VERSION, - }); + const { data, meta, ...rest } = await this.http.post( + LENS_VIS_API_PATH, + { + body: JSON.stringify(body), + query: options, + version: LENS_API_VERSION, + } + ); return { - item: ConfigBuilderStub.in(data), + item: { + ...rest, + ...data, + }, meta, }; } @@ -86,39 +100,35 @@ export class LensClient { id: string, { description, visualizationType, state, title, version }: LooseLensAttributes, references: Reference[], - options: LensUpdateRequestBody['options'] = {} - ) { + options: LensUpdateRequestQuery = {} + ): Promise { if (visualizationType === null) { throw new Error('Missing visualization type'); } const body: LensUpdateRequestBody = { - // TODO: Find a better way to conditionally omit id - data: omit( - ConfigBuilderStub.out({ - id: '', - description, - visualizationType, - state, - title, - version, - references, - }), - 'id' - ), - options, + description, + visualizationType, + state, + title, + version, + references, }; - const { data, meta } = await this.http.put( + const { data, meta, ...rest } = await this.http.put( `${LENS_VIS_API_PATH}/${id}`, { body: JSON.stringify(body), + query: options, version: LENS_API_VERSION, } ); return { - item: ConfigBuilderStub.in(data), + item: { + ...rest, + ...data, + }, meta, }; } @@ -140,7 +150,6 @@ export class LensClient { fields, searchFields, }: LensSearchRequestQuery): Promise { - // TODO add all CM search options to query const result = await this.http.get(LENS_VIS_API_PATH, { query: { query, @@ -152,9 +161,11 @@ export class LensClient { version: LENS_API_VERSION, }); - return result.data.map(({ data }) => ({ - ...data, - attributes: ConfigBuilderStub.in(data), - })); + return result.data.map(({ id, data }) => { + return { + id, + ...data, + } satisfies LensItem; + }); } } diff --git a/x-pack/platform/plugins/shared/lens/public/plugin.ts b/x-pack/platform/plugins/shared/lens/public/plugin.ts index 711af3ee9f2f5..94acf11b4c4fa 100644 --- a/x-pack/platform/plugins/shared/lens/public/plugin.ts +++ b/x-pack/platform/plugins/shared/lens/public/plugin.ts @@ -390,9 +390,12 @@ export class LensPlugin { return createLensEmbeddableFactory(deps); }); - embeddable.registerTransforms(LENS_EMBEDDABLE_TYPE, async () => { + embeddable.registerLegacyURLTransform(LENS_EMBEDDABLE_TYPE, async () => { const { getLensTransforms } = await import('../common/transforms'); - return getLensTransforms(); + return getLensTransforms({ + transformEnhancementsIn: embeddable.transformEnhancementsIn, + transformEnhancementsOut: embeddable.transformEnhancementsOut, + }).transformOut; }); // Let Dashboard know about the Lens panel type diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts index 46cfb9c5e5d93..f171c1728055a 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Reference } from '@kbn/content-management-utils'; import type { ViewMode } from '@kbn/presentation-publishing'; import { apiHasParentApi, @@ -13,13 +12,13 @@ import { getInheritedViewMode, type PublishingSubject, apiHasExecutionContext, - findSavedObjectRef, } from '@kbn/presentation-publishing'; import { isObject } from 'lodash'; import { BehaviorSubject } from 'rxjs'; import { isOfAggregateQueryType } from '@kbn/es-query'; import type { RenderMode } from '@kbn/expressions-plugin/common'; import type { + LensByValueSerializedState, LensEmbeddableStartServices, LensRuntimeState, LensSerializedState, @@ -30,7 +29,6 @@ import { loadESQLAttributes } from './esql'; import type { DatasourceStates, GeneralDatasourceStates } from '../state_management'; import type { FormBasedPersistedState } from '../datasources/form_based/types'; import type { TextBasedPersistedState } from '../datasources/form_based/esql_layer/types'; -import { DOC_TYPE } from '../../common/constants'; import { LENS_ITEM_LATEST_VERSION } from '../../common/constants'; export function createEmptyLensState( @@ -69,29 +67,26 @@ export async function deserializeState( attributeService, ...services }: Pick & ESQLStartServices, - rawState: LensSerializedState, - references?: Reference[] + { savedObjectId, ...state }: LensSerializedState ): Promise { - const fallbackAttributes = createEmptyLensState().attributes; - const savedObjectRef = findSavedObjectRef(DOC_TYPE, references); - const savedObjectId = savedObjectRef?.id ?? rawState.savedObjectId; - if (savedObjectId) { try { const { attributes, managed, sharingSavedObjectProps } = await attributeService.loadFromLibrary(savedObjectId); - return { ...rawState, savedObjectId, attributes, managed, sharingSavedObjectProps }; + return { + ...state, + savedObjectId, + attributes, + managed, + sharingSavedObjectProps, + } satisfies LensRuntimeState; } catch (e) { // return an empty Lens document if no saved object is found - return { ...rawState, attributes: fallbackAttributes }; + return { ...state, attributes: createEmptyLensState().attributes }; } } - // Inject applied only to by-value SOs - const newState = attributeService.injectReferences( - ('attributes' in rawState ? rawState : { attributes: rawState }) as LensRuntimeState, - references?.length ? references : undefined - ); + const newState = transformInitialState(state) as LensRuntimeState; if (newState.isNewPanel) { try { @@ -99,11 +94,11 @@ export async function deserializeState( // provide a fallback return { ...newState, - attributes: newAttributes ?? newState.attributes ?? fallbackAttributes, + attributes: newAttributes ?? newState.attributes ?? createEmptyLensState().attributes, }; } catch (e) { // return an empty Lens document if no saved object is found - return { ...newState, attributes: fallbackAttributes }; + return { ...newState, attributes: createEmptyLensState().attributes }; } } @@ -163,3 +158,13 @@ export function getStructuredDatasourceStates( undefined) as TextBasedPersistedState, }; } + +export function transformInitialState(state: LensSerializedState): LensSerializedState { + // TODO add api conversion + return state; +} + +export function transformOutputState(state: LensSerializedState): LensByValueSerializedState { + // TODO add api conversion + return state; +} diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.ts index 6fb7699dd3669..7decb73303088 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.ts @@ -11,41 +11,26 @@ import { isOfAggregateQueryType, } from '@kbn/es-query'; import { omit } from 'lodash'; -import type { Reference } from '@kbn/content-management-utils'; -import { - SAVED_OBJECT_REF_NAME, - type HasSerializableState, - type SerializedPanelState, -} from '@kbn/presentation-publishing'; -import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; -import { isTextBasedLanguage } from '../helper'; +import { type HasSerializableState, type SerializedPanelState } from '@kbn/presentation-publishing'; +import { isTextBasedLanguage, transformOutputState } from '../helper'; import type { GetStateType, - LensEmbeddableStartServices, - LensInternalApi, + LensByRefSerializedState, + LensByValueSerializedState, LensRuntimeState, + LensSerializedState, + LensInternalApi, } from '../types'; import type { IntegrationCallbacks } from '../types'; -import { DOC_TYPE } from '../../../common/constants'; -function cleanupSerializedState({ - rawState, - references, -}: { - rawState: LensRuntimeState; - references: Reference[]; -}) { - const cleanedState = omit(rawState, 'searchSessionId'); - return { - rawState: cleanedState, - references, - }; +function cleanupSerializedState(state: LensRuntimeState) { + const cleanedState = omit(state, 'searchSessionId'); + + return cleanedState; } export function initializeIntegrations( getLatestState: GetStateType, - serializeDynamicActions: (() => SerializedPanelState) | undefined, - { attributeService }: LensEmbeddableStartServices, internalApi: LensInternalApi ): { api: Omit< @@ -58,7 +43,7 @@ export function initializeIntegrations( | 'updateDataLoading' | 'getTriggerCompatibleActions' > & - HasSerializableState; + HasSerializableState; } { return { api: { @@ -66,38 +51,25 @@ export function initializeIntegrations( * This API is used by the parent to serialize the panel state to save it into its saved object. * Make sure to remove the attributes when the panel is by reference. */ - serializeState: () => { - const currentState = getLatestState(); - const cleanedState = cleanupSerializedState( - attributeService.extractReferences(currentState) - ); - const { rawState: dynamicActionsState, references: dynamicActionsReferences } = - serializeDynamicActions?.() ?? {}; - if (cleanedState.rawState.savedObjectId) { - const { savedObjectId, attributes, ...byRefState } = cleanedState.rawState; + serializeState: (): SerializedPanelState => { + const currentState = cleanupSerializedState(getLatestState()); + + const { savedObjectId, attributes, ...state } = currentState; + + if (savedObjectId) { return { rawState: { - ...byRefState, - ...dynamicActionsState, + ...state, + savedObjectId, }, - references: [ - ...cleanedState.references, - ...(dynamicActionsReferences ?? []), - { - name: SAVED_OBJECT_REF_NAME, - type: DOC_TYPE, - id: savedObjectId, - }, - ], - }; + } satisfies SerializedPanelState; } + + const transformedState = transformOutputState(currentState); + return { - rawState: { - ...cleanedState.rawState, - ...dynamicActionsState, - }, - references: [...cleanedState.references, ...(dynamicActionsReferences ?? [])], - }; + rawState: transformedState, + } satisfies SerializedPanelState; }, // TODO: workout why we have this duplicated getFullAttributes: () => getLatestState().attributes, 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 3ec0f5dc5ebbd..3b9f21b1fe3bf 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 @@ -65,11 +65,7 @@ export const createLensEmbeddableFactory = ( initialState ); - const initialRuntimeState = await deserializeState( - services, - initialState.rawState, - initialState.references - ); + const initialRuntimeState = await deserializeState(services, initialState.rawState); /** * Observables and functions declared here are used internally to store mutating state values @@ -119,11 +115,7 @@ export const createLensEmbeddableFactory = ( parentApi ); - const integrationsConfig = initializeIntegrations( - getLatestState, - dynamicActionsManager?.serializeState, - internalApi - ); + const integrationsConfig = initializeIntegrations(getLatestState, internalApi); const actionsConfig = initializeActionApi( uuid, initialRuntimeState, @@ -173,11 +165,7 @@ export const createLensEmbeddableFactory = ( dashboardConfig.reinitializeState(lastSaved?.rawState); searchContextConfig.reinitializeState(lastSaved?.rawState); if (!lastSaved) return; - const lastSavedRuntimeState = await deserializeState( - services, - lastSaved.rawState, - lastSaved.references - ); + const lastSavedRuntimeState = await deserializeState(services, lastSaved.rawState); stateConfig.reinitializeRuntimeState(lastSavedRuntimeState); }, }); diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts index d666f1bc53e0f..65dee801077c8 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/types.ts @@ -186,6 +186,9 @@ interface ContentManagementProps { } interface LensWithReferences { + /** + * @deprecated use `state.attributes.references` + */ references?: Reference[]; } diff --git a/x-pack/platform/plugins/shared/lens/server/plugin.tsx b/x-pack/platform/plugins/shared/lens/server/plugin.tsx index dc983ab9ef48f..fbfe58a05e2e9 100644 --- a/x-pack/platform/plugins/shared/lens/server/plugin.tsx +++ b/x-pack/platform/plugins/shared/lens/server/plugin.tsx @@ -115,7 +115,13 @@ export class LensServerPlugin lensEmbeddableFactory() as unknown as EmbeddableRegistryDefinition ); - plugins.embeddable.registerTransforms(LENS_EMBEDDABLE_TYPE, getLensTransforms()); + plugins.embeddable.registerTransforms( + LENS_EMBEDDABLE_TYPE, + getLensTransforms({ + transformEnhancementsIn: plugins.embeddable.transformEnhancementsIn, + transformEnhancementsOut: plugins.embeddable.transformEnhancementsOut, + }) + ); registerLensAPIRoutes({ http: core.http, From c91a92ff6aaafdbe8a5d5ebe518e0887ef056079 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 22 Oct 2025 13:05:36 -0700 Subject: [PATCH 03/15] fix type errors --- .../shared/cases/server/common/utils.ts | 12 +- .../initialize_integrations.test.ts | 9 +- .../shared/lens/server/embeddable/types.ts | 2 +- .../api_integration/apis/lens/examples.ts | 107 +++++++++--------- 4 files changed, 62 insertions(+), 68 deletions(-) diff --git a/x-pack/platform/plugins/shared/cases/server/common/utils.ts b/x-pack/platform/plugins/shared/cases/server/common/utils.ts index 9ae93b099c543..f4949e3c1b50a 100644 --- a/x-pack/platform/plugins/shared/cases/server/common/utils.ts +++ b/x-pack/platform/plugins/shared/cases/server/common/utils.ts @@ -15,6 +15,7 @@ import type { import { flatMap, uniqWith, xorWith } from 'lodash'; import type { LensServerPluginSetup } from '@kbn/lens-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import type { LensEmbeddableStateWithType } from '@kbn/lens-plugin/server/embeddable/types'; import type { ActionsAttachmentPayload, AlertAttachmentPayload, @@ -402,15 +403,16 @@ export const extractLensReferencesFromCommentString = ( lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'], comment: string ): SavedObjectReference[] => { - const extract = lensEmbeddableFactory()?.extract; + const extract = lensEmbeddableFactory().extract; if (extract) { const parsedComment = parseCommentString(comment); const lensVisualizations = getLensVisualizations(parsedComment.children); - const flattenRefs = flatMap( - lensVisualizations, - (lensObject) => extract(lensObject)?.references ?? [] - ); + const flattenRefs = flatMap(lensVisualizations, (vis) => { + // TODO: Improve these types + const lensVis = vis as unknown as LensEmbeddableStateWithType; + return extract(lensVis).references; + }); const uniqRefs = uniqWith( flattenRefs, diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts index d63e7b7290277..6d1c08c3705ef 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts @@ -7,18 +7,13 @@ import { faker } from '@faker-js/faker'; import { createEmptyLensState } from '../helper'; -import { makeEmbeddableServices, getLensRuntimeStateMock } from '../mocks'; +import { getLensRuntimeStateMock } from '../mocks'; import type { LensRuntimeState } from '../types'; import { initializeIntegrations } from './initialize_integrations'; function setupIntegrationsApi(stateOverrides?: Partial) { - const services = makeEmbeddableServices(undefined, undefined, { - visOverrides: { id: 'lnsXY' }, - dataOverrides: { id: 'formBased' }, - }); const runtimeState = getLensRuntimeStateMock(stateOverrides); - const serializeDynamicActions = undefined; - const { api } = initializeIntegrations(() => runtimeState, serializeDynamicActions, services); + const { api } = initializeIntegrations(() => runtimeState); return api; } diff --git a/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts b/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts index 0c0ee9bc0a215..a63f4ad0aa580 100644 --- a/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts +++ b/x-pack/platform/plugins/shared/lens/server/embeddable/types.ts @@ -13,7 +13,7 @@ import type { import type { LensRuntimeState } from '../../public'; import type { DOC_TYPE } from '../../common/constants'; -type LensEmbeddableStateWithType = EmbeddableStateWithType & +export type LensEmbeddableStateWithType = EmbeddableStateWithType & LensRuntimeState & { type: typeof DOC_TYPE; }; diff --git a/x-pack/platform/test/api_integration/apis/lens/examples.ts b/x-pack/platform/test/api_integration/apis/lens/examples.ts index 9a0ac294ba786..e129f5c43a25b 100644 --- a/x-pack/platform/test/api_integration/apis/lens/examples.ts +++ b/x-pack/platform/test/api_integration/apis/lens/examples.ts @@ -11,69 +11,66 @@ export const getExampleLensBody = ( title = `Lens vis - ${Date.now()} - ${Math.random()}`, description = '' ): LensCreateRequestBody => ({ - data: { - title, - description, - visualizationType: 'lnsMetric', - state: { - visualization: { - layerId: '32e889c6-89f9-4873-b1f7-d5bea381c582', - layerType: 'data', - metricAccessor: '1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8', - secondaryTrend: { - type: 'none', - }, - }, - query: { - query: '', - language: 'kuery', + title, + description, + visualizationType: 'lnsMetric', + state: { + visualization: { + layerId: '32e889c6-89f9-4873-b1f7-d5bea381c582', + layerType: 'data', + metricAccessor: '1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8', + secondaryTrend: { + type: 'none', }, - filters: [], - datasourceStates: { - formBased: { - layers: { - '32e889c6-89f9-4873-b1f7-d5bea381c582': { - columns: { - '1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8': { - label: 'Count of records', - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: '___records___', - params: { - emptyAsNull: true, - }, + }, + query: { + query: '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + '32e889c6-89f9-4873-b1f7-d5bea381c582': { + columns: { + '1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8': { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: true, }, }, - columnOrder: ['1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8'], - incompleteColumns: { - 'd0b92889-f74c-4194-b738-76eb5d268524': { - operationType: 'date_histogram', - }, + }, + columnOrder: ['1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8'], + incompleteColumns: { + 'd0b92889-f74c-4194-b738-76eb5d268524': { + operationType: 'date_histogram', }, - sampling: 1, }, + sampling: 1, }, }, - indexpattern: { - layers: {}, - }, - textBased: { - layers: {}, - }, }, - internalReferences: [], - adHocDataViews: {}, - isNewApiFormat: true, // temporary flag - }, - references: [ - { - type: 'index-pattern', - id: '91200a00-9efd-11e7-acb3-3dab96693fab', - name: 'indexpattern-datasource-layer-32e889c6-89f9-4873-b1f7-d5bea381c582', + indexpattern: { + layers: {}, }, - ], + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + isNewApiFormat: true, // temporary flag }, - options: {}, + references: [ + { + type: 'index-pattern', + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + name: 'indexpattern-datasource-layer-32e889c6-89f9-4873-b1f7-d5bea381c582', + }, + ], }); From 3f21b73ecfd7e8a844c866e6e03c6bec65ee4015 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Oct 2025 14:10:45 +0200 Subject: [PATCH 04/15] :label: first type pass --- .../kbn-lens-common/embeddable/types.ts | 39 +++++++++++++------ .../packages/shared/kbn-lens-common/index.ts | 5 ++- .../packages/shared/kbn-lens-common/types.ts | 8 ---- .../lens/common/transforms/transform_out.ts | 2 +- .../shared/lens/common/transforms/types.ts | 2 +- .../shared/lens/common/transforms/utils.ts | 3 +- .../public/react_embeddable/renderer/types.ts | 3 +- 7 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts index 6867434eb77ee..75c4412118d42 100644 --- a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts @@ -126,9 +126,9 @@ export interface PreventableEvent { preventDefault(): void; } -interface LensByValue { - // by-value - attributes?: Simplify; +export interface LensByValueBase { + savedObjectId?: string; + attributes?: LensSavedObjectAttributes; } export interface LensOverrides { @@ -150,10 +150,9 @@ export interface LensOverrides { /** * Lens embeddable props broken down by type */ - -export interface LensByReference { - // by-reference - savedObjectId?: string; +interface LensByReferenceBase { + savedObjectId?: string; // really should be never + attributes?: never; } interface ContentManagementProps { @@ -161,9 +160,12 @@ interface ContentManagementProps { managed?: boolean; } -export type LensPropsVariants = (LensByValue & LensByReference) & { +interface LensWithReferences { + /** + * @deprecated use `state.attributes.references` + */ references?: Reference[]; -}; +} export interface ViewInDiscoverCallbacks extends LensApiProps { canViewUnderlyingData$: PublishingSubject; @@ -259,9 +261,9 @@ interface LensRequestHandlersProps { * * Panel settings * * other props from the embeddable */ -export type LensSerializedState = Simplify< - LensPropsVariants & - LensOverrides & +type LensSerializedSharedState = Simplify< + LensOverrides & + LensWithReferences & LensUnifiedSearchContext & LensPanelProps & SerializedTitles & @@ -269,6 +271,19 @@ export type LensSerializedState = Simplify< Partial & { isNewPanel?: boolean } >; +export type LensByValueSerializedState = Simplify; +export type LensByRefSerializedState = Simplify; + +/** + * Combined properties of serialized state stored on dashboard panel + * + * Includes: + * - Lens document state (for by-value) + * - Panel settings + * - other props from the embeddable + */ +export type LensSerializedState = LensByRefSerializedState | LensByValueSerializedState; + /** * Custom props exposed on the Lens exported component */ diff --git a/src/platform/packages/shared/kbn-lens-common/index.ts b/src/platform/packages/shared/kbn-lens-common/index.ts index 88a2cbe71d483..2bdd948ddc208 100644 --- a/src/platform/packages/shared/kbn-lens-common/index.ts +++ b/src/platform/packages/shared/kbn-lens-common/index.ts @@ -252,8 +252,8 @@ export type { DocumentToExpressionReturnType, PreventableEvent, LensOverrides, - LensByReference, - LensPropsVariants, + LensByValueSerializedState, + LensByRefSerializedState, ViewInDiscoverCallbacks, IntegrationCallbacks, LensPublicCallbacks, @@ -276,6 +276,7 @@ export type { TypedLensSerializedState, LensEmbeddableOutput, ESQLVariablesCompatibleDashboardApi, + LensByValueBase, } from './embeddable/types'; export type { LensAppLocatorParams, diff --git a/src/platform/packages/shared/kbn-lens-common/types.ts b/src/platform/packages/shared/kbn-lens-common/types.ts index cb10e18d15228..d680b7f58ab57 100644 --- a/src/platform/packages/shared/kbn-lens-common/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/types.ts @@ -144,14 +144,6 @@ export interface LensAttributesService { savedObjectId?: string ) => Promise; checkForDuplicateTitle: (props: CheckDuplicateTitleProps) => Promise<{ isDuplicate: boolean }>; - injectReferences: ( - runtimeState: LensRuntimeState, - references: Reference[] | undefined - ) => LensRuntimeState; - extractReferences: (runtimeState: LensRuntimeState) => { - rawState: LensRuntimeState; - references: Reference[]; - }; } export interface LensAppServices extends StartServices { 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 ed0662122aa17..e1ed8a33a922a 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 @@ -6,8 +6,8 @@ */ import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public'; +import type { LensByValueSerializedState } from '@kbn/lens-common'; import type { LensTransformDependencies } from '.'; -import type { LensByValueSerializedState } from '../../public/react_embeddable/types'; import { LENS_ITEM_VERSION_V1, transformToV1LensItemAttributes } from '../content_management/v1'; import { injectLensReferences } from '../references'; import type { diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/types.ts b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts index 49a37127ece8a..8964e0a88b706 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/types.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts @@ -11,7 +11,7 @@ import type { LensSerializedState, LensByRefSerializedState, LensByValueSerializedState, -} from '../../public/react_embeddable/types'; +} from '@kbn/lens-common'; export type LensTransforms = Required< EmbeddableTransforms diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts index bbadad13dcaf3..19dc9e432d920 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/utils.ts @@ -7,8 +7,7 @@ import type { Reference } from '@kbn/content-management-utils'; -import type { LensSerializedState } from '../../public'; -import type { LensByRefSerializedState } from '../../public/react_embeddable/types'; +import type { LensByRefSerializedState, LensSerializedState } from '@kbn/lens-common'; import { DOC_TYPE } from '../constants'; export const LENS_SAVED_OBJECT_REF_NAME = 'savedObjectRef'; diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts index 45018d1cd6ace..e7aa75035a9f5 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts @@ -10,8 +10,7 @@ import type { BehaviorSubject } from 'rxjs'; import type { ESQLControlVariable } from '@kbn/esql-types'; import type { ViewMode, useSearchApi } from '@kbn/presentation-publishing'; import type { HasSerializedChildState } from '@kbn/presentation-containers'; - -import type { LensRuntimeState, LensSerializedState } from '../types'; +import type { LensRuntimeState, LensSerializedState } from '@kbn/lens-common'; type SearchApi = ReturnType; From d7aa151acc55570a506e3a709cb7dc3ade9e60d5 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Oct 2025 14:15:53 +0200 Subject: [PATCH 05/15] :label: More types fixed --- src/platform/packages/shared/kbn-lens-common/types.ts | 6 +----- .../platform/plugins/shared/lens/common/transforms/index.ts | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/packages/shared/kbn-lens-common/types.ts b/src/platform/packages/shared/kbn-lens-common/types.ts index d680b7f58ab57..674410b8dacf5 100644 --- a/src/platform/packages/shared/kbn-lens-common/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/types.ts @@ -79,11 +79,7 @@ import type { InspectorOptions } from '@kbn/inspector-plugin/public'; import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import type { NavigateToLensContext } from './convert_to_lens_types'; import type { LensAppLocator, MainHistoryLocationState } from './locator_types'; -import type { - LensRuntimeState, - LensSavedObjectAttributes, - StructuredDatasourceStates, -} from './embeddable/types'; +import type { LensSavedObjectAttributes, StructuredDatasourceStates } from './embeddable/types'; import type { DimensionLink, LensConfiguration, diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts index 14e6c71efd652..f2f4ab2424bf4 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts @@ -21,6 +21,7 @@ export function getLensTransforms(deps: LensTransformDependencies): LensTransfor return { transformIn: getTransformIn(deps), transformOut: getTransformOut(deps), + transformOutInjectsReferences: true, schema: schema.any(), }; } From c023bcc75c5d4914036fa8e0a4f3852953daafa4 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Oct 2025 14:35:00 +0200 Subject: [PATCH 06/15] :wrench: Flag package for treeshake --- .../packages/shared/kbn-lens-embeddable-utils/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-lens-embeddable-utils/package.json b/src/platform/packages/shared/kbn-lens-embeddable-utils/package.json index 71b5c4008a1d9..a225a8c3dedc4 100644 --- a/src/platform/packages/shared/kbn-lens-embeddable-utils/package.json +++ b/src/platform/packages/shared/kbn-lens-embeddable-utils/package.json @@ -3,5 +3,6 @@ "private": true, "version": "1.0.0", "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", - "homepage": "https://docs.elastic.dev/kibana-dev-docs/lens/config-builder" + "homepage": "https://docs.elastic.dev/kibana-dev-docs/lens/config-builder", + "sideEffects": false } \ No newline at end of file From 7319d75abbe73ea16b60478cdf2500ecd7511c31 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Oct 2025 15:24:37 +0200 Subject: [PATCH 07/15] :white_check_mark: Align test to lack of references --- .../initializers/initialize_integrations.test.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts index 8f731e77948ff..2c26c9fe21902 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/initializers/initialize_integrations.test.ts @@ -36,7 +36,7 @@ describe('Dashboard services API', () => { // * savedObjectId is cleaned up expect(rawState).not.toHaveProperty('savedObjectId'); // * references should be at root level - expect(references).toEqual(attributes.references); + expect(references).toBeUndefined(); }); it('should serialize state for a by-reference panel', async () => { const attributes = createAttributesWithReferences(); @@ -47,15 +47,7 @@ describe('Dashboard services API', () => { const { rawState, references } = api.serializeState(); // check the same 3 things as above expect(rawState).not.toEqual(expect.objectContaining({ attributes: expect.anything() })); - // * references should be at root level - expect(references).toEqual([ - ...attributes.references, - { - id: '123', - name: 'savedObjectRef', - type: 'lens', - }, - ]); + expect(references).toBeUndefined(); }); it('should remove the searchSessionId from the serializedState', async () => { From 19a76e7351522f8eafcf46b61b8073f0b8414d8c Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 27 Oct 2025 19:22:30 -0700 Subject: [PATCH 08/15] chore: remove `schema` on transform definition --- .../platform/plugins/shared/lens/common/transforms/index.ts | 2 -- .../platform/plugins/shared/lens/common/transforms/types.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts index f2f4ab2424bf4..c8e3d24929c15 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import type { EnhancementsRegistry } from '@kbn/embeddable-plugin/common/enhancements/registry'; import type { LensTransforms } from './types'; @@ -22,6 +21,5 @@ export function getLensTransforms(deps: LensTransformDependencies): LensTransfor transformIn: getTransformIn(deps), transformOut: getTransformOut(deps), transformOutInjectsReferences: true, - schema: schema.any(), }; } diff --git a/x-pack/platform/plugins/shared/lens/common/transforms/types.ts b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts index 8964e0a88b706..2a69e8b26e39f 100644 --- a/x-pack/platform/plugins/shared/lens/common/transforms/types.ts +++ b/x-pack/platform/plugins/shared/lens/common/transforms/types.ts @@ -6,6 +6,7 @@ */ import type { EmbeddableTransforms } from '@kbn/embeddable-plugin/common'; +import type { Required } from 'utility-types'; import type { LensSerializedState, @@ -14,7 +15,8 @@ import type { } from '@kbn/lens-common'; export type LensTransforms = Required< - EmbeddableTransforms + EmbeddableTransforms, + 'transformIn' | 'transformOut' >; /** From 5c507dac075f31f76a30167ae909e07434cd04bb Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 27 Oct 2025 19:37:35 -0700 Subject: [PATCH 09/15] chore: move `LensParentApi` types to `@kbn/lens-common` --- .../kbn-lens-common/embeddable/types.ts | 27 ++++++++++++++ .../packages/shared/kbn-lens-common/index.ts | 1 + .../lens_custom_renderer_component.tsx | 8 +++-- .../public/react_embeddable/renderer/types.ts | 36 ------------------- 4 files changed, 34 insertions(+), 38 deletions(-) delete mode 100644 x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts diff --git a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts index 75c4412118d42..4df087f814e3f 100644 --- a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts @@ -7,6 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { BehaviorSubject } from 'rxjs'; + +import type { HasSerializedChildState } from '@kbn/presentation-containers'; import type { AggregateQuery, ExecutionContextSearch, @@ -44,6 +47,7 @@ import type { PublishingSubject, SerializedTitles, ViewMode, + useSearchApi, } from '@kbn/presentation-publishing'; import type { Action } from '@kbn/ui-actions-plugin/public'; import type { @@ -531,3 +535,26 @@ export interface ESQLVariablesCompatibleDashboardApi { controlGroupApi$: PublishingSubject | undefined>; children$: PublishingSubject<{ [key: string]: unknown }>; } + +type SearchApi = ReturnType; + +interface GeneralLensApi { + searchSessionId$: BehaviorSubject; + disabledActionIds$: BehaviorSubject; + setDisabledActionIds: (ids: string[] | undefined) => void; + viewMode$: BehaviorSubject; + settings: { + syncColors$: BehaviorSubject; + syncCursor$: BehaviorSubject; + syncTooltips$: BehaviorSubject; + }; + forceDSL?: boolean; + esqlVariables$: BehaviorSubject; + hideTitle$: BehaviorSubject; + reload$: BehaviorSubject; +} + +export type LensParentApi = SearchApi & + LensRuntimeState & + GeneralLensApi & + HasSerializedChildState; diff --git a/src/platform/packages/shared/kbn-lens-common/index.ts b/src/platform/packages/shared/kbn-lens-common/index.ts index 2bdd948ddc208..d26c9e57ea5e7 100644 --- a/src/platform/packages/shared/kbn-lens-common/index.ts +++ b/src/platform/packages/shared/kbn-lens-common/index.ts @@ -268,6 +268,7 @@ export type { LensHasEditPanel, LensInspectorAdapters, LensApi, + LensParentApi, LensInternalApi, ExpressionWrapperProps, GetStateType, diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx index 364d9b3728c89..5b8bb90c6b211 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx @@ -10,10 +10,14 @@ import { useSearchApi } from '@kbn/presentation-publishing'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { BehaviorSubject } from 'rxjs'; import type { PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; -import type { LensApi, LensRendererProps, LensSerializedState } from '@kbn/lens-common'; +import type { + LensApi, + LensParentApi, + LensRendererProps, + LensSerializedState, +} from '@kbn/lens-common'; import { LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; import { createEmptyLensState, transformOutputState } from '../helper'; -import type { LensParentApi } from './types'; // This little utility uses the same pattern of the useSearchApi hook: // create the Subject once and then update its value on change diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts deleted file mode 100644 index e7aa75035a9f5..0000000000000 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/renderer/types.ts +++ /dev/null @@ -1,36 +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 { BehaviorSubject } from 'rxjs'; - -import type { ESQLControlVariable } from '@kbn/esql-types'; -import type { ViewMode, useSearchApi } from '@kbn/presentation-publishing'; -import type { HasSerializedChildState } from '@kbn/presentation-containers'; -import type { LensRuntimeState, LensSerializedState } from '@kbn/lens-common'; - -type SearchApi = ReturnType; - -interface GeneralLensApi { - searchSessionId$: BehaviorSubject; - disabledActionIds$: BehaviorSubject; - setDisabledActionIds: (ids: string[] | undefined) => void; - viewMode$: BehaviorSubject; - settings: { - syncColors$: BehaviorSubject; - syncCursor$: BehaviorSubject; - syncTooltips$: BehaviorSubject; - }; - forceDSL?: boolean; - esqlVariables$: BehaviorSubject; - hideTitle$: BehaviorSubject; - reload$: BehaviorSubject; -} - -export type LensParentApi = SearchApi & - LensRuntimeState & - GeneralLensApi & - HasSerializedChildState; From 3385eb17fd7b6d3414a4061e9f0cc7c0abf20738 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 27 Oct 2025 19:38:02 -0700 Subject: [PATCH 10/15] chore: revert code cleanup --- .../plugins/shared/lens/public/react_embeddable/helper.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts index 1743dc80bd102..003c77d874680 100644 --- a/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts +++ b/x-pack/platform/plugins/shared/lens/public/react_embeddable/helper.ts @@ -70,6 +70,8 @@ export async function deserializeState( }: Pick & ESQLStartServices, { savedObjectId, ...state }: LensSerializedState ): Promise { + const fallbackAttributes = createEmptyLensState().attributes; + if (savedObjectId) { try { const { attributes, managed, sharingSavedObjectProps } = @@ -83,7 +85,7 @@ export async function deserializeState( } satisfies LensRuntimeState; } catch (e) { // return an empty Lens document if no saved object is found - return { ...state, attributes: createEmptyLensState().attributes }; + return { ...state, attributes: fallbackAttributes }; } } @@ -95,11 +97,11 @@ export async function deserializeState( // provide a fallback return { ...newState, - attributes: newAttributes ?? newState.attributes ?? createEmptyLensState().attributes, + attributes: newAttributes ?? newState.attributes ?? fallbackAttributes, }; } catch (e) { // return an empty Lens document if no saved object is found - return { ...newState, attributes: createEmptyLensState().attributes }; + return { ...newState, attributes: fallbackAttributes }; } } From 4ad0c86a4bc07a93f2e3dccd4e6f249270f8fdbf Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 27 Oct 2025 20:07:58 -0700 Subject: [PATCH 11/15] fix: lens api integration tests --- .../apis/lens/visualizations/create/validation.ts | 2 +- .../apis/lens/visualizations/update/validation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/platform/test/api_integration/apis/lens/visualizations/create/validation.ts b/x-pack/platform/test/api_integration/apis/lens/visualizations/create/validation.ts index 19e58e58b1e08..de4d1f305dcca 100644 --- a/x-pack/platform/test/api_integration/apis/lens/visualizations/create/validation.ts +++ b/x-pack/platform/test/api_integration/apis/lens/visualizations/create/validation.ts @@ -23,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.status).to.be(400); expect(response.body.message).to.be( - '[request body.data]: expected at least one defined value but got [undefined]' + '[request body]: types that failed validation:\n- [request body.0.references]: expected value of type [array] but got [undefined]\n- [request body.1.references]: expected value of type [array] but got [undefined]' ); }); }); diff --git a/x-pack/platform/test/api_integration/apis/lens/visualizations/update/validation.ts b/x-pack/platform/test/api_integration/apis/lens/visualizations/update/validation.ts index e1c4c0e269397..d1c6a420a1be4 100644 --- a/x-pack/platform/test/api_integration/apis/lens/visualizations/update/validation.ts +++ b/x-pack/platform/test/api_integration/apis/lens/visualizations/update/validation.ts @@ -24,7 +24,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.status).to.be(400); expect(response.body.message).to.be( - '[request body.data.title]: expected value of type [string] but got [undefined]' + '[request body]: types that failed validation:\n- [request body.0.references]: expected value of type [array] but got [undefined]\n- [request body.1.references]: expected value of type [array] but got [undefined]' ); }); }); From 8b2a35e3e1a15a86e4968a2c62e8816791bf7204 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 28 Oct 2025 04:02:39 +0000 Subject: [PATCH 12/15] [CI] Auto-commit changed files from 'node scripts/eslint_all_files --no-cache --fix' --- .../sections/rule_details/components/rule_details.test.tsx | 2 +- .../components/use_rule_description_fields.test.tsx | 2 +- .../rule_details/components/use_rule_description_fields.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index 1f84f80028ebe..c09bdc0222762 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -31,7 +31,7 @@ import { import { useKibana } from '../../../../common/lib/kibana'; import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; import { createMockConnectorType } from '@kbn/actions-plugin/server/application/connector/mocks'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; const queryClient = new QueryClient({ defaultOptions: { diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.test.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.test.tsx index a992c7c2e7fbc..42b3ed2ff8fbe 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.test.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { AsyncField, createPrebuildFields } from './use_rule_description_fields'; import { screen, render } from '@testing-library/react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; import type { HttpSetup } from '@kbn/core/public'; import { RULE_PREBUILD_DESCRIPTION_FIELDS } from './rule_detail_description_type'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.tsx index 0a9aed2806ce1..4c139b2d231eb 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rule_details/components/use_rule_description_fields.tsx @@ -8,7 +8,7 @@ import React, { Suspense, useMemo } from 'react'; import { EuiBadge, EuiCodeBlock, EuiFlexGroup, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery } from '@kbn/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { HttpResponse, HttpSetup } from '@kbn/core/public'; import { RULE_PREBUILD_DESCRIPTION_FIELDS } from './rule_detail_description_type'; From e8fc9db4c71efceb8798faf9c1951c651a33e55b Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 28 Oct 2025 16:25:40 -0700 Subject: [PATCH 13/15] fix add panel from library --- .../shared/kbn-lens-common/embeddable/types.ts | 4 ++-- .../plugins/shared/lens/public/plugin.ts | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts index 4df087f814e3f..ec535a8c7fd76 100644 --- a/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/embeddable/types.ts @@ -131,7 +131,7 @@ export interface PreventableEvent { } export interface LensByValueBase { - savedObjectId?: string; + savedObjectId?: string; // really should be never but creates type issues attributes?: LensSavedObjectAttributes; } @@ -155,7 +155,7 @@ export interface LensOverrides { * Lens embeddable props broken down by type */ interface LensByReferenceBase { - savedObjectId?: string; // really should be never + savedObjectId?: string; attributes?: never; } diff --git a/x-pack/platform/plugins/shared/lens/public/plugin.ts b/x-pack/platform/plugins/shared/lens/public/plugin.ts index 5199db2992ac3..e929e0e14ae8c 100644 --- a/x-pack/platform/plugins/shared/lens/public/plugin.ts +++ b/x-pack/platform/plugins/shared/lens/public/plugin.ts @@ -62,6 +62,7 @@ import type { VisualizeEditorContext, EditorFrameSetup, LensDocument, + LensByRefSerializedState, } from '@kbn/lens-common'; import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; @@ -395,21 +396,14 @@ export class LensPlugin { // Let Dashboard know about the Lens panel type embeddable.registerAddFromLibraryType({ - onAdd: async (container, savedObject) => { - const { SAVED_OBJECT_REF_NAME } = await import('@kbn/presentation-publishing'); + onAdd: (container, savedObject) => { container.addNewPanel( { panelType: LENS_EMBEDDABLE_TYPE, serializedState: { - rawState: {}, - references: [ - ...savedObject.references, - { - name: SAVED_OBJECT_REF_NAME, - type: LENS_EMBEDDABLE_TYPE, - id: savedObject.id, - }, - ], + rawState: { + savedObjectId: savedObject.id, + } satisfies LensByRefSerializedState, }, }, true From 05215fed0c56c911111ec42e7c4420bb0a8010b1 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 29 Oct 2025 12:53:36 -0700 Subject: [PATCH 14/15] fix: background session issue bg sessions use the public url transforms which defaults to references from the dashboard url which are now always empty, soon to be removed entirely. But in Lens we had logic to filter references from the dashboard based on references from the lens SO. Instead we should just merge all refs from the lens SO and dashboard. --- .../shared/lens/common/references/index.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/common/references/index.ts b/x-pack/platform/plugins/shared/lens/common/references/index.ts index 0b490fc71374e..9d1745e765385 100644 --- a/x-pack/platform/plugins/shared/lens/common/references/index.ts +++ b/x-pack/platform/plugins/shared/lens/common/references/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { cloneDeep } from 'lodash'; +import { cloneDeep, uniqBy } from 'lodash'; import type { Reference } from '@kbn/content-management-utils'; @@ -21,20 +21,10 @@ export const injectLensReferences = ( return clonedState; } - // match references based on name, so only references associated with this lens panel are injected. - const matchedReferences: Reference[] = []; - - if (Array.isArray(clonedState.attributes.references)) { - clonedState.attributes.references.forEach((serializableRef) => { - const internalReference = serializableRef; - const matchedReference = references.find( - (reference) => reference.name === internalReference.name - ); - if (matchedReference) matchedReferences.push(matchedReference); - }); - } + // TODO: find a way to cull erroneous dashboard references + const combinedReferences = uniqBy([...references, ...clonedState.attributes.references], 'name'); - clonedState.attributes.references = matchedReferences; + clonedState.attributes.references = combinedReferences; return clonedState; }; From b2b01af75d5d87780384f68733c94fe4f7a963f1 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Wed, 29 Oct 2025 18:28:58 -0700 Subject: [PATCH 15/15] chore: update cloud limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a8006e2a2abd0..0e96c4c99af47 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -11,7 +11,7 @@ pageLoadAssetSize: canvas: 15210 cases: 153204 charts: 40269 - cloud: 9300 + cloud: 9320 cloudDataMigration: 5687 cloudExperiments: 103984 cloudFullStory: 4752