diff --git a/packages/kbn-content-management-utils/index.ts b/packages/kbn-content-management-utils/index.ts index fa5b1780bac20..ea5bda32c98a9 100644 --- a/packages/kbn-content-management-utils/index.ts +++ b/packages/kbn-content-management-utils/index.ts @@ -10,3 +10,4 @@ export * from './src/types'; export * from './src/schema'; export * from './src/saved_object_content_storage'; export * from './src/utils'; +export * from './src/msearch'; diff --git a/packages/kbn-content-management-utils/src/msearch.ts b/packages/kbn-content-management-utils/src/msearch.ts new file mode 100644 index 0000000000000..8b22d993374e6 --- /dev/null +++ b/packages/kbn-content-management-utils/src/msearch.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Boom from '@hapi/boom'; +import { pick } from 'lodash'; + +import type { StorageContext } from '@kbn/content-management-plugin/server'; + +import type { + SavedObjectsFindResult, + SavedObject, + SavedObjectReference, +} from '@kbn/core-saved-objects-api-server'; + +import type { ServicesDefinitionSet, SOWithMetadata, SOWithMetadataPartial } from './types'; + +type PartialSavedObject = Omit>, 'references'> & { + references: SavedObjectReference[] | undefined; +}; + +interface GetMSearchParams { + savedObjectType: string; + cmServicesDefinition: ServicesDefinitionSet; + allowedSavedObjectAttributes: string[]; +} + +function savedObjectToItem( + savedObject: SavedObject | PartialSavedObject, + allowedSavedObjectAttributes: string[] +): SOWithMetadata | SOWithMetadataPartial { + const { + id, + type, + updated_at: updatedAt, + created_at: createdAt, + attributes, + references, + error, + namespaces, + version, + } = savedObject; + + return { + id, + type, + updatedAt, + createdAt, + attributes: pick(attributes, allowedSavedObjectAttributes), + references, + error, + namespaces, + version, + }; +} + +export interface GetMSearchType { + savedObjectType: string; + toItemResult: (ctx: StorageContext, savedObject: SavedObjectsFindResult) => ReturnItem; +} + +export const getMSearch = ({ + savedObjectType, + cmServicesDefinition, + allowedSavedObjectAttributes, +}: GetMSearchParams) => { + return { + savedObjectType, + toItemResult: (ctx: StorageContext, savedObject: SavedObjectsFindResult): ReturnItem => { + const transforms = ctx.utils.getTransforms(cmServicesDefinition); + + // Validate DB response and DOWN transform to the request version + const { value, error: resultError } = transforms.mSearch.out.result.down< + ReturnItem, + ReturnItem + >( + // Ran into a case where a schema was broken by a SO attribute that wasn't part of the definition + // so we specify which attributes are allowed + savedObjectToItem( + savedObject as SavedObjectsFindResult, + allowedSavedObjectAttributes + ) + ); + + if (resultError) { + throw Boom.badRequest(`Invalid response. ${resultError.message}`); + } + + return value; + }, + }; +}; diff --git a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts index 890b9f9d5bec6..f71e9cd72b43b 100644 --- a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts +++ b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts @@ -15,6 +15,8 @@ import type { SavedObjectsFindOptions, } from '@kbn/core-saved-objects-api-server'; +import { getMSearch, type GetMSearchType } from '@kbn/content-management-utils'; + import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common'; import { cmServicesDefinition } from '../../common/content_management/cm_services'; import type { @@ -96,7 +98,20 @@ export class EventAnnotationGroupStorage implements ContentStorage { - constructor() {} + mSearch: GetMSearchType; + constructor() { + this.mSearch = getMSearch({ + savedObjectType: SO_TYPE, + cmServicesDefinition, + allowedSavedObjectAttributes: [ + 'title', + 'description', + 'ignoreGlobalFilters', + 'annotations', + 'dataViewSpec', + ], + }); + } async get(ctx: StorageContext, id: string): Promise { const { diff --git a/src/plugins/event_annotation/tsconfig.json b/src/plugins/event_annotation/tsconfig.json index e65a50d3f75e2..a14cebb9541e6 100644 --- a/src/plugins/event_annotation/tsconfig.json +++ b/src/plugins/event_annotation/tsconfig.json @@ -41,7 +41,8 @@ "@kbn/core-lifecycle-browser", "@kbn/saved-objects-tagging-oss-plugin", "@kbn/dom-drag-drop", - "@kbn/kibana-utils-plugin" + "@kbn/kibana-utils-plugin", + "@kbn/content-management-utils" ], "exclude": [ "target/**/*", diff --git a/src/plugins/visualizations/server/content_management/visualization_storage.ts b/src/plugins/visualizations/server/content_management/visualization_storage.ts index ef2b1181080a4..5b5e99a7132aa 100644 --- a/src/plugins/visualizations/server/content_management/visualization_storage.ts +++ b/src/plugins/visualizations/server/content_management/visualization_storage.ts @@ -14,6 +14,8 @@ import type { SavedObjectsFindOptions, } from '@kbn/core-saved-objects-api-server'; +import { getMSearch, type GetMSearchType } from '@kbn/content-management-utils'; + import { CONTENT_ID } from '../../common/content_management'; import { cmServicesDefinition } from '../../common/content_management/cm_services'; import type { @@ -103,7 +105,23 @@ const SO_TYPE: VisualizationContentType = 'visualization'; export class VisualizationsStorage implements ContentStorage { - constructor() {} + mSearch: GetMSearchType; + + constructor() { + this.mSearch = getMSearch({ + savedObjectType: SO_TYPE, + cmServicesDefinition, + allowedSavedObjectAttributes: [ + 'title', + 'description', + 'version', + 'kibanaSavedObjectMeta', + 'uiStateJSON', + 'visState', + 'savedSearchRefName', + ], + }); + } async get(ctx: StorageContext, id: string): Promise { const { diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index 65e1053e4f7e1..4df8ede52adcb 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -61,7 +61,8 @@ "@kbn/core-saved-objects-utils-server", "@kbn/content-management-table-list-view-table", "@kbn/content-management-tabbed-table-list-view", - "@kbn/content-management-table-list-view" + "@kbn/content-management-table-list-view", + "@kbn/content-management-utils" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/lens/server/content_management/lens_storage.ts b/x-pack/plugins/lens/server/content_management/lens_storage.ts index 0de3fc38d8a07..c394508a0d476 100644 --- a/x-pack/plugins/lens/server/content_management/lens_storage.ts +++ b/x-pack/plugins/lens/server/content_management/lens_storage.ts @@ -13,6 +13,8 @@ import type { SavedObjectsFindOptions, } from '@kbn/core-saved-objects-api-server'; +import { getMSearch, type GetMSearchType } from '@kbn/content-management-utils'; + import { CONTENT_ID } from '../../common/content_management'; import { cmServicesDefinition } from '../../common/content_management/cm_services'; import type { @@ -91,7 +93,14 @@ function savedObjectToLensSavedObject( const SO_TYPE: LensContentType = 'lens'; export class LensStorage implements ContentStorage { - constructor() {} + mSearch: GetMSearchType; + constructor() { + this.mSearch = getMSearch({ + savedObjectType: SO_TYPE, + cmServicesDefinition, + allowedSavedObjectAttributes: ['title', 'description', 'visualizationType', 'state'], + }); + } async get(ctx: StorageContext, id: string): Promise { const { diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 541f3e98277b9..23dfd600e2048 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -83,6 +83,7 @@ "@kbn/core-overlays-browser-mocks", "@kbn/core-theme-browser-mocks", "@kbn/event-annotation-components", + "@kbn/content-management-utils", ], "exclude": [ "target/**/*",