From d9de67f6d15c4bed37757a5439802bfa5d0645be Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 11 Jan 2021 10:36:02 +0100 Subject: [PATCH 01/18] initial POC --- src/core/server/index.ts | 2 + src/core/server/legacy/legacy_service.ts | 1 + src/core/server/plugins/plugin_context.ts | 1 + .../export/apply_export_hooks.ts | 63 +++++++++++++++++++ src/core/server/saved_objects/export/index.ts | 2 + .../export/saved_objects_exporter.test.ts | 22 ++++++- .../export/saved_objects_exporter.ts | 15 +++++ src/core/server/saved_objects/export/types.ts | 20 +++++- src/core/server/saved_objects/index.ts | 2 + .../server/saved_objects/routes/export.ts | 11 +++- .../saved_objects_service.mock.ts | 1 + .../saved_objects/saved_objects_service.ts | 17 ++++- .../lib/copy_to_spaces/copy_to_spaces.ts | 1 + .../copy_to_spaces/resolve_copy_conflicts.ts | 1 + 14 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 src/core/server/saved_objects/export/apply_export_hooks.ts diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0dae17b4c211e..87f55b69f9d74 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -324,6 +324,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportError, + SavedObjectsTypeExportHook, + SavedObjectsExportContext, SavedObjectsImporter, ISavedObjectsImporter, SavedObjectsImportError, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 609555e4e34c1..9173bf7981d05 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -267,6 +267,7 @@ export class LegacyService implements CoreService { setClientFactoryProvider: setupDeps.core.savedObjects.setClientFactoryProvider, addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, registerType: setupDeps.core.savedObjects.registerType, + registerExportHook: setupDeps.core.savedObjects.registerExportHook, }, status: { isStatusPageAnonymous: setupDeps.core.status.isStatusPageAnonymous, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 42f44e4405443..e2f439462337d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -188,6 +188,7 @@ export function createPluginSetupContext( setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, addClientWrapper: deps.savedObjects.addClientWrapper, registerType: deps.savedObjects.registerType, + registerExportHook: deps.savedObjects.registerExportHook, }, status: { core$: deps.status.core$, diff --git a/src/core/server/saved_objects/export/apply_export_hooks.ts b/src/core/server/saved_objects/export/apply_export_hooks.ts new file mode 100644 index 0000000000000..eb0a83750e10d --- /dev/null +++ b/src/core/server/saved_objects/export/apply_export_hooks.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObject } from '../../../types'; +import { KibanaRequest } from '../../http'; +import { SavedObjectsTypeExportHook, SavedObjectsExportContext } from './types'; + +interface ApplyExportHooksOptions { + objects: SavedObject[]; + request: KibanaRequest; + exportHooks: Record; +} + +// TODO: doc + add tests. +export const applyExportHooks = async ({ + objects, + request, + exportHooks, +}: ApplyExportHooksOptions): Promise => { + const context = createContext(request); + const byType = splitByType(objects); + + let finalObjects: SavedObject[] = []; + for (const [type, typeObjs] of Object.entries(byType)) { + const typeHook = exportHooks[type]; + if (typeHook) { + finalObjects = [...finalObjects, ...(await typeHook(typeObjs, context))]; + } else { + finalObjects = [...finalObjects, ...typeObjs]; + } + } + + return finalObjects; +}; + +const createContext = (request: KibanaRequest): SavedObjectsExportContext => { + return { + request, + }; +}; + +const splitByType = (objects: SavedObject[]): Record => { + return objects.reduce((memo, obj) => { + memo[obj.type] = [...(memo[obj.type] ?? []), obj]; + return memo; + }, {} as Record); +}; diff --git a/src/core/server/saved_objects/export/index.ts b/src/core/server/saved_objects/export/index.ts index 5166f20b3d1c1..883b6edc9bca3 100644 --- a/src/core/server/saved_objects/export/index.ts +++ b/src/core/server/saved_objects/export/index.ts @@ -22,6 +22,8 @@ export { SavedObjectExportBaseOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportResultDetails, + SavedObjectsExportContext, + SavedObjectsTypeExportHook, } from './types'; export { ISavedObjectsExporter, SavedObjectsExporter } from './saved_objects_exporter'; export { SavedObjectsExportError } from './errors'; diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index b382a36a35ef7..bd47255842988 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -19,6 +19,7 @@ import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; +import { httpServerMock } from '../../http/http_server.mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; @@ -27,6 +28,8 @@ async function readStreamToCompletion(stream: Readable) { } const exportSizeLimit = 500; +const request = httpServerMock.createKibanaRequest(); +const exportHooks = {}; describe('getSortedObjectsForExport()', () => { let savedObjectsClient: ReturnType; @@ -34,7 +37,7 @@ describe('getSortedObjectsForExport()', () => { beforeEach(() => { savedObjectsClient = savedObjectsClientMock.create(); - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, exportHooks }); }); describe('#exportByTypes', () => { @@ -67,6 +70,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }); @@ -157,6 +161,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }); @@ -245,6 +250,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], excludeExportDetails: true, }); @@ -304,6 +310,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], search: 'foo', }); @@ -386,6 +393,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], hasReference: [ { @@ -479,6 +487,7 @@ describe('getSortedObjectsForExport()', () => { page: 0, }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], namespace: 'foo', }); @@ -542,7 +551,7 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected types throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, exportHooks }); savedObjectsClient.find.mockResolvedValueOnce({ total: 2, @@ -573,6 +582,7 @@ describe('getSortedObjectsForExport()', () => { }); await expect( exporter.exportByTypes({ + request, types: ['index-pattern', 'search'], }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Can't export more than 1 objects"`); @@ -614,6 +624,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByTypes({ + request, types: ['index-pattern'], }); const response = await readStreamToCompletion(exportStream); @@ -678,6 +689,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'index-pattern', @@ -770,6 +782,7 @@ describe('getSortedObjectsForExport()', () => { }); await expect( exporter.exportByObjects({ + request, objects: [ { type: 'index-pattern', @@ -785,9 +798,10 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected objects throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1 }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, exportHooks }); const exportOpts = { + request, objects: [ { type: 'index-pattern', @@ -814,6 +828,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'multi', id: '1' }, { type: 'multi', id: '2' }, @@ -857,6 +872,7 @@ describe('getSortedObjectsForExport()', () => { ], }); const exportStream = await exporter.exportByObjects({ + request, objects: [ { type: 'search', diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index 94b21dda56be1..0dcf920b1f2b5 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -27,8 +27,10 @@ import { SavedObjectExportBaseOptions, SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, + SavedObjectsTypeExportHook, } from './types'; import { SavedObjectsExportError } from './errors'; +import { applyExportHooks } from './apply_export_hooks'; /** * @public @@ -40,16 +42,20 @@ export type ISavedObjectsExporter = PublicMethodsOf; */ export class SavedObjectsExporter { readonly #savedObjectsClient: SavedObjectsClientContract; + readonly #exportHooks: Record; readonly #exportSizeLimit: number; constructor({ savedObjectsClient, + exportHooks, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + exportHooks: Record; exportSizeLimit: number; }) { this.#savedObjectsClient = savedObjectsClient; + this.#exportHooks = exportHooks; this.#exportSizeLimit = exportSizeLimit; } @@ -63,6 +69,7 @@ export class SavedObjectsExporter { public async exportByTypes(options: SavedObjectsExportByTypeOptions) { const objects = await this.fetchByTypes(options); return this.processObjects(objects, { + request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, namespace: options.namespace, @@ -82,6 +89,7 @@ export class SavedObjectsExporter { } const objects = await this.fetchByObjects(options); return this.processObjects(objects, { + request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, namespace: options.namespace, @@ -91,6 +99,7 @@ export class SavedObjectsExporter { private async processObjects( savedObjects: SavedObject[], { + request, excludeExportDetails = false, includeReferencesDeep = false, namespace, @@ -99,6 +108,12 @@ export class SavedObjectsExporter { let exportedObjects: Array>; let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = []; + savedObjects = await applyExportHooks({ + objects: savedObjects, + request, + exportHooks: this.#exportHooks, + }); + if (includeReferencesDeep) { const fetchResult = await fetchNestedDependencies( savedObjects, diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index 0ddcdc361c896..b0b0c4b2c8364 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -17,10 +17,13 @@ * under the License. */ -import { SavedObjectsFindOptionsReference } from '../types'; +import { KibanaRequest } from '../../http'; +import { SavedObject, SavedObjectsFindOptionsReference } from '../types'; /** @public */ export interface SavedObjectExportBaseOptions { + /** The http request initiating the export. */ + request: KibanaRequest; /** flag to also include all related saved objects in the export stream. */ includeReferencesDeep?: boolean; /** flag to not append {@link SavedObjectsExportResultDetails | export details} to the end of the export stream. */ @@ -75,3 +78,18 @@ export interface SavedObjectsExportResultDetails { type: string; }>; } + +/** + * @public + */ +export interface SavedObjectsExportContext { + request: KibanaRequest; +} + +/** + * @public + */ +export type SavedObjectsTypeExportHook = ( + objects: Array>, + context: SavedObjectsExportContext +) => SavedObject[] | Promise; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 2d9e2f2b247fe..5efe1ddd3d249 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -44,6 +44,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportResultDetails, SavedObjectsExportError, + SavedObjectsExportContext, + SavedObjectsTypeExportHook, } from './export'; export { diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 6343e535f4db3..1ec9e2cdf2f3d 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -21,7 +21,7 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils'; -import { IRouter } from '../../http'; +import { IRouter, KibanaRequest } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { @@ -89,7 +89,11 @@ const validateOptions = ( includeReferencesDeep, search, }: ExportOptions, - { exportSizeLimit, supportedTypes }: { exportSizeLimit: number; supportedTypes: string[] } + { + exportSizeLimit, + supportedTypes, + request, + }: { exportSizeLimit: number; supportedTypes: string[]; request: KibanaRequest } ): EitherExportOptions => { const hasTypes = (types?.length ?? 0) > 0; const hasObjects = (objects?.length ?? 0) > 0; @@ -117,6 +121,7 @@ const validateOptions = ( objects: objects!, excludeExportDetails, includeReferencesDeep, + request, }; } else { const validationError = validateTypes(types!, supportedTypes); @@ -129,6 +134,7 @@ const validateOptions = ( search, excludeExportDetails, includeReferencesDeep, + request, }; } }; @@ -176,6 +182,7 @@ export const registerExportRoute = ( let options: EitherExportOptions; try { options = validateOptions(cleaned, { + request: req, exportSizeLimit: maxImportExportSize, supportedTypes, }); diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 1a920501541b6..3d3005e625f53 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -72,6 +72,7 @@ const createSetupContractMock = () => { setClientFactoryProvider: jest.fn(), addClientWrapper: jest.fn(), registerType: jest.fn(), + registerExportHook: jest.fn(), }; return setupContract; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index c34da35a35531..dc27369a42740 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -49,11 +49,12 @@ import { import { Logger } from '../logging'; import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; import { SavedObjectsSerializer } from './serialization'; -import { SavedObjectsExporter, ISavedObjectsExporter } from './export'; +import { SavedObjectsExporter, ISavedObjectsExporter, SavedObjectsTypeExportHook } from './export'; import { SavedObjectsImporter, ISavedObjectsImporter } from './import'; import { registerRoutes } from './routes'; import { ServiceStatus } from '../status'; import { calculateStatus$ } from './status'; + /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to * use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods @@ -151,6 +152,11 @@ export interface SavedObjectsServiceSetup { * ``` */ registerType: (type: SavedObjectsType) => void; + + /** + * TODO: documentation + */ + registerExportHook: (type: string, exportHook: SavedObjectsTypeExportHook) => void; } /** @@ -280,6 +286,7 @@ export class SavedObjectsService private config?: SavedObjectConfig; private clientFactoryProvider?: SavedObjectsClientFactoryProvider; private clientFactoryWrappers: WrappedClientFactoryWrapper[] = []; + private exportHooks: Map = new Map(); private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); @@ -345,6 +352,13 @@ export class SavedObjectsService } this.typeRegistry.registerType(type); }, + registerExportHook: (type, hook) => { + if (this.started) { + throw new Error('cannot call `registerExportHook` after service startup.'); + } + // TODO + this.exportHooks.set(type, hook); + }, }; } @@ -458,6 +472,7 @@ export class SavedObjectsService createExporter: (savedObjectsClient) => new SavedObjectsExporter({ savedObjectsClient, + exportHooks: Object.fromEntries(this.exportHooks), exportSizeLimit: this.config!.maxImportExportSize, }), createImporter: (savedObjectsClient) => diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts index 852f680b0245a..39f31c5f85178 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts @@ -29,6 +29,7 @@ export function copySavedObjectsToSpacesFactory( options: Pick ) => { const objectStream = await savedObjectsExporter.exportByObjects({ + request, namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, excludeExportDetails: true, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts index 2a671b1423e8c..6033e369d3ea4 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts @@ -29,6 +29,7 @@ export function resolveCopySavedObjectsToSpacesConflictsFactory( options: Pick ) => { const objectStream = await savedObjectsExporter.exportByObjects({ + request, namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, excludeExportDetails: true, From 53996eea2160b1d25eb762055f48a59ee2463d6c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 11 Jan 2021 12:19:13 +0100 Subject: [PATCH 02/18] fix spaces UT --- .../spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts | 1 + .../server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index db731713811b4..4f1f8ba5be3e5 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -166,6 +166,7 @@ describe('copySavedObjectsToSpaces', () => { `); expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({ + request: expect.any(Object), excludeExportDetails: true, includeReferencesDeep: true, namespace, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index a558044d413d1..2850e6cb09906 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -173,6 +173,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { `); expect(savedObjectsExporter.exportByObjects).toHaveBeenCalledWith({ + request: expect.any(Object), excludeExportDetails: true, includeReferencesDeep: true, namespace, From af9523f63b0deeba18a27bd81a4e9b7cf2e999c1 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 15 Jan 2021 11:49:26 +0100 Subject: [PATCH 03/18] address POC feedback, add tests for applyExportTransforms --- src/core/server/index.ts | 4 +- src/core/server/legacy/legacy_service.ts | 1 - src/core/server/plugins/plugin_context.ts | 1 - .../export/apply_export_hooks.ts | 63 ---- .../export/apply_export_transforms.test.ts | 311 ++++++++++++++++++ .../export/apply_export_transforms.ts | 103 ++++++ .../server/saved_objects/export/errors.ts | 28 ++ src/core/server/saved_objects/export/index.ts | 4 +- .../export/saved_objects_exporter.test.ts | 10 +- .../export/saved_objects_exporter.ts | 27 +- src/core/server/saved_objects/export/types.ts | 12 +- src/core/server/saved_objects/index.ts | 4 +- .../saved_objects_service.mock.ts | 1 - .../saved_objects/saved_objects_service.ts | 17 +- src/core/server/saved_objects/types.ts | 7 + 15 files changed, 489 insertions(+), 104 deletions(-) delete mode 100644 src/core/server/saved_objects/export/apply_export_hooks.ts create mode 100644 src/core/server/saved_objects/export/apply_export_transforms.test.ts create mode 100644 src/core/server/saved_objects/export/apply_export_transforms.ts diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 87f55b69f9d74..6790f7e2a10f6 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -324,8 +324,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportError, - SavedObjectsTypeExportHook, - SavedObjectsExportContext, + SavedObjectsExportTransform, + SavedObjectsExportTransformContext, SavedObjectsImporter, ISavedObjectsImporter, SavedObjectsImportError, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 9173bf7981d05..609555e4e34c1 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -267,7 +267,6 @@ export class LegacyService implements CoreService { setClientFactoryProvider: setupDeps.core.savedObjects.setClientFactoryProvider, addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, registerType: setupDeps.core.savedObjects.registerType, - registerExportHook: setupDeps.core.savedObjects.registerExportHook, }, status: { isStatusPageAnonymous: setupDeps.core.status.isStatusPageAnonymous, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index e2f439462337d..42f44e4405443 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -188,7 +188,6 @@ export function createPluginSetupContext( setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, addClientWrapper: deps.savedObjects.addClientWrapper, registerType: deps.savedObjects.registerType, - registerExportHook: deps.savedObjects.registerExportHook, }, status: { core$: deps.status.core$, diff --git a/src/core/server/saved_objects/export/apply_export_hooks.ts b/src/core/server/saved_objects/export/apply_export_hooks.ts deleted file mode 100644 index eb0a83750e10d..0000000000000 --- a/src/core/server/saved_objects/export/apply_export_hooks.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { SavedObject } from '../../../types'; -import { KibanaRequest } from '../../http'; -import { SavedObjectsTypeExportHook, SavedObjectsExportContext } from './types'; - -interface ApplyExportHooksOptions { - objects: SavedObject[]; - request: KibanaRequest; - exportHooks: Record; -} - -// TODO: doc + add tests. -export const applyExportHooks = async ({ - objects, - request, - exportHooks, -}: ApplyExportHooksOptions): Promise => { - const context = createContext(request); - const byType = splitByType(objects); - - let finalObjects: SavedObject[] = []; - for (const [type, typeObjs] of Object.entries(byType)) { - const typeHook = exportHooks[type]; - if (typeHook) { - finalObjects = [...finalObjects, ...(await typeHook(typeObjs, context))]; - } else { - finalObjects = [...finalObjects, ...typeObjs]; - } - } - - return finalObjects; -}; - -const createContext = (request: KibanaRequest): SavedObjectsExportContext => { - return { - request, - }; -}; - -const splitByType = (objects: SavedObject[]): Record => { - return objects.reduce((memo, obj) => { - memo[obj.type] = [...(memo[obj.type] ?? []), obj]; - return memo; - }, {} as Record); -}; diff --git a/src/core/server/saved_objects/export/apply_export_transforms.test.ts b/src/core/server/saved_objects/export/apply_export_transforms.test.ts new file mode 100644 index 0000000000000..b27d1e747f57f --- /dev/null +++ b/src/core/server/saved_objects/export/apply_export_transforms.test.ts @@ -0,0 +1,311 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObject } from '../../../types'; +import { KibanaRequest } from '../../http'; +import { httpServerMock } from '../../http/http_server.mocks'; +import { applyExportTransforms } from './apply_export_transforms'; +import { SavedObjectsExportTransform } from './types'; + +const createObj = ( + type: string, + id: string, + attributes: Record = {} +): SavedObject => ({ + type, + id, + attributes, + references: [], +}); + +const createTransform = ( + implementation: SavedObjectsExportTransform = (ctx, objs) => objs +): jest.MockedFunction => jest.fn(implementation); + +const expectedContext = { + request: expect.any(KibanaRequest), +}; + +describe('applyExportTransforms', () => { + let request: ReturnType; + + beforeEach(() => { + request = httpServerMock.createKibanaRequest(); + }); + + it('calls the transform functions with the correct parameters', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + + const fooTransform = createTransform(); + const barTransform = createTransform(); + + await applyExportTransforms({ + request, + objects: [foo1, bar1, foo2], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(fooTransform).toHaveBeenCalledTimes(1); + expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1, foo2]); + + expect(barTransform).toHaveBeenCalledTimes(1); + expect(barTransform).toHaveBeenCalledWith(expectedContext, [bar1]); + }); + + it('does not call the transform functions if no objects are present', async () => { + const foo1 = createObj('foo', '1'); + + const fooTransform = createTransform(); + const barTransform = createTransform(); + + await applyExportTransforms({ + request, + objects: [foo1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(fooTransform).toHaveBeenCalledTimes(1); + expect(fooTransform).toHaveBeenCalledWith(expectedContext, [foo1]); + + expect(barTransform).not.toHaveBeenCalled(); + }); + + it('allows to add objects to the export', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + const dolly1 = createObj('dolly', '1'); + const hello1 = createObj('hello', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1, foo2], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(result).toEqual([foo1, foo2, dolly1, bar1, hello1]); + }); + + it('returns unmutated objects if no transform is defined for the type', async () => { + const foo1 = createObj('foo', '1'); + const foo2 = createObj('foo', '2'); + const bar1 = createObj('bar', '1'); + const bar2 = createObj('bar', '2'); + const dolly1 = createObj('dolly', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, foo2, bar1, bar2], + transforms: { + foo: fooTransform, + }, + }); + + expect(result).toEqual([foo1, foo2, dolly1, bar1, bar2]); + }); + + it('allows to mutate objects', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const disableFoo = (obj: SavedObject) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + }, + }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map(disableFoo); + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }); + + expect(result).toEqual([foo1, foo2].map(disableFoo)); + }); + + it('supports async transforms', async () => { + const foo1 = createObj('foo', '1'); + const bar1 = createObj('bar', '1'); + const dolly1 = createObj('dolly', '1'); + const hello1 = createObj('hello', '1'); + + const fooTransform = createTransform((ctx, objs) => { + return Promise.resolve([...objs, dolly1]); + }); + + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + }); + + expect(result).toEqual([foo1, dolly1, bar1, hello1]); + }); + + it('uses the provided sortFunction when provided', async () => { + const foo1 = createObj('foo', 'A'); + const bar1 = createObj('bar', 'B'); + const dolly1 = createObj('dolly', 'C'); + const hello1 = createObj('hello', 'D'); + + const fooTransform = createTransform((ctx, objs) => { + return [...objs, dolly1]; + }); + + const barTransform = createTransform((ctx, objs) => { + return [...objs, hello1]; + }); + + const result = await applyExportTransforms({ + request, + objects: [foo1, bar1], + transforms: { + foo: fooTransform, + bar: barTransform, + }, + sortFunction: (obj1, obj2) => (obj1.id > obj2.id ? 1 : -1), + }); + + expect(result).toEqual([foo1, bar1, dolly1, hello1]); + }); + + it('throws when removing objects', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return [objs[0]]; + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws when changing the object type', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + type: 'mutated', + })); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws when changing the object id', async () => { + const foo1 = createObj('foo', '1', { enabled: true }); + const foo2 = createObj('foo', '2', { enabled: true }); + + const fooTransform = createTransform((ctx, objs) => { + return objs.map((obj, idx) => ({ + ...obj, + id: `mutated-${idx}`, + })); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1, foo2], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid transform performed on objects to export"` + ); + }); + + it('throws if the transform function throws', async () => { + const foo1 = createObj('foo', '1'); + + const fooTransform = createTransform(() => { + throw new Error('oups.'); + }); + + await expect( + applyExportTransforms({ + request, + objects: [foo1], + transforms: { + foo: fooTransform, + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Error transforming objects to export"`); + }); +}); diff --git a/src/core/server/saved_objects/export/apply_export_transforms.ts b/src/core/server/saved_objects/export/apply_export_transforms.ts new file mode 100644 index 0000000000000..871865a5ef20e --- /dev/null +++ b/src/core/server/saved_objects/export/apply_export_transforms.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObject } from '../../../types'; +import { KibanaRequest } from '../../http'; +import { SavedObjectsExportError } from './errors'; +import { SavedObjectsExportTransform, SavedObjectsExportTransformContext } from './types'; + +interface ApplyExportTransformsOptions { + objects: SavedObject[]; + request: KibanaRequest; + transforms: Record; + sortFunction?: (obj1: SavedObject, obj2: SavedObject) => number; +} + +export const applyExportTransforms = async ({ + objects, + request, + transforms, + sortFunction, +}: ApplyExportTransformsOptions): Promise => { + const context = createContext(request); + const byType = splitByType(objects); + + let finalObjects: SavedObject[] = []; + for (const [type, typeObjs] of Object.entries(byType)) { + const typeTransformFn = transforms[type]; + if (typeTransformFn) { + finalObjects = [ + ...finalObjects, + ...(await applyTransform(typeObjs, typeTransformFn, context)), + ]; + } else { + finalObjects = [...finalObjects, ...typeObjs]; + } + } + + if (sortFunction) { + finalObjects.sort(sortFunction); + } + + return finalObjects; +}; + +const applyTransform = async ( + objs: SavedObject[], + transformFn: SavedObjectsExportTransform, + context: SavedObjectsExportTransformContext +) => { + const objKeys = objs.map(getObjKey); + let transformedObjects: SavedObject[]; + try { + transformedObjects = await transformFn(context, objs); + } catch (e) { + throw SavedObjectsExportError.objectTransformError(objs, e); + } + assertValidTransform(transformedObjects, objKeys); + return transformedObjects; +}; + +const createContext = (request: KibanaRequest): SavedObjectsExportTransformContext => { + return { + request, + }; +}; + +const splitByType = (objects: SavedObject[]): Record => { + return objects.reduce((memo, obj) => { + memo[obj.type] = [...(memo[obj.type] ?? []), obj]; + return memo; + }, {} as Record); +}; + +const getObjKey = (obj: SavedObject) => `${obj.type}|${obj.id}`; + +const assertValidTransform = (transformedObjects: SavedObject[], initialKeys: string[]) => { + const transformedKeys = transformedObjects.map(getObjKey); + const missingKeys: string[] = []; + initialKeys.forEach((initialKey) => { + if (!transformedKeys.includes(initialKey)) { + missingKeys.push(initialKey); + } + }); + if (missingKeys.length) { + throw SavedObjectsExportError.invalidTransformError(missingKeys); + } +}; diff --git a/src/core/server/saved_objects/export/errors.ts b/src/core/server/saved_objects/export/errors.ts index 3a26b092ab489..33cd9f6775790 100644 --- a/src/core/server/saved_objects/export/errors.ts +++ b/src/core/server/saved_objects/export/errors.ts @@ -47,4 +47,32 @@ export class SavedObjectsExportError extends Error { objects, }); } + + /** + * Error returned when a {@link SavedObjectsExportTransform | export tranform} threw an error + */ + static objectTransformError(objects: SavedObject[], cause: Error) { + return new SavedObjectsExportError( + 'object-transform-error', + 'Error transforming objects to export', + { + objects, + cause: cause.message, + } + ); + } + + /** + * Error returned when a {@link SavedObjectsExportTransform | export tranform} performed an invalid operation + * during the transform, such as removing objects from the export, or changing an object's type or id. + */ + static invalidTransformError(objectKeys: string[]) { + return new SavedObjectsExportError( + 'object-transform-error', + 'Invalid transform performed on objects to export', + { + objectKeys, + } + ); + } } diff --git a/src/core/server/saved_objects/export/index.ts b/src/core/server/saved_objects/export/index.ts index 883b6edc9bca3..1c0e9e2962d67 100644 --- a/src/core/server/saved_objects/export/index.ts +++ b/src/core/server/saved_objects/export/index.ts @@ -22,8 +22,8 @@ export { SavedObjectExportBaseOptions, SavedObjectsExportByTypeOptions, SavedObjectsExportResultDetails, - SavedObjectsExportContext, - SavedObjectsTypeExportHook, + SavedObjectsExportTransformContext, + SavedObjectsExportTransform, } from './types'; export { ISavedObjectsExporter, SavedObjectsExporter } from './saved_objects_exporter'; export { SavedObjectsExportError } from './errors'; diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index bd47255842988..cbdf7d98a841a 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -19,6 +19,7 @@ import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; +import { SavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { httpServerMock } from '../../http/http_server.mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; @@ -29,15 +30,16 @@ async function readStreamToCompletion(stream: Readable) { const exportSizeLimit = 500; const request = httpServerMock.createKibanaRequest(); -const exportHooks = {}; describe('getSortedObjectsForExport()', () => { let savedObjectsClient: ReturnType; + let typeRegistry: SavedObjectTypeRegistry; let exporter: SavedObjectsExporter; beforeEach(() => { + typeRegistry = new SavedObjectTypeRegistry(); savedObjectsClient = savedObjectsClientMock.create(); - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, exportHooks }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry }); }); describe('#exportByTypes', () => { @@ -551,7 +553,7 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected types throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, exportHooks }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry }); savedObjectsClient.find.mockResolvedValueOnce({ total: 2, @@ -798,7 +800,7 @@ describe('getSortedObjectsForExport()', () => { }); test('export selected objects throws error when exceeding exportSizeLimit', async () => { - exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, exportHooks }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit: 1, typeRegistry }); const exportOpts = { request, diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index 0dcf920b1f2b5..cdcbe7b987976 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -20,6 +20,7 @@ import { createListStream } from '@kbn/utils'; import { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObject, SavedObjectsClientContract } from '../types'; +import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; import { fetchNestedDependencies } from './fetch_nested_dependencies'; import { sortObjects } from './sort_objects'; import { @@ -27,10 +28,10 @@ import { SavedObjectExportBaseOptions, SavedObjectsExportByObjectOptions, SavedObjectsExportByTypeOptions, - SavedObjectsTypeExportHook, + SavedObjectsExportTransform, } from './types'; import { SavedObjectsExportError } from './errors'; -import { applyExportHooks } from './apply_export_hooks'; +import { applyExportTransforms } from './apply_export_transforms'; /** * @public @@ -42,21 +43,29 @@ export type ISavedObjectsExporter = PublicMethodsOf; */ export class SavedObjectsExporter { readonly #savedObjectsClient: SavedObjectsClientContract; - readonly #exportHooks: Record; + readonly #exportTransforms: Record; readonly #exportSizeLimit: number; constructor({ savedObjectsClient, - exportHooks, + typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; - exportHooks: Record; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }) { this.#savedObjectsClient = savedObjectsClient; - this.#exportHooks = exportHooks; this.#exportSizeLimit = exportSizeLimit; + this.#exportTransforms = typeRegistry.getAllTypes().reduce((transforms, type) => { + if (type.management?.onExport) { + return { + ...transforms, + [type.name]: type.management.onExport, + }; + } + return transforms; + }, {} as Record); } /** @@ -108,10 +117,10 @@ export class SavedObjectsExporter { let exportedObjects: Array>; let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = []; - savedObjects = await applyExportHooks({ - objects: savedObjects, + savedObjects = await applyExportTransforms({ request, - exportHooks: this.#exportHooks, + objects: savedObjects, + transforms: this.#exportTransforms, }); if (includeReferencesDeep) { diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index b0b0c4b2c8364..2a48d05d25f56 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -80,16 +80,20 @@ export interface SavedObjectsExportResultDetails { } /** + * Context passed down to a {@link SavedObjectsExportTransform | export transform function} + * * @public */ -export interface SavedObjectsExportContext { +export interface SavedObjectsExportTransformContext { request: KibanaRequest; } /** + * TODO: doc + examples + * * @public */ -export type SavedObjectsTypeExportHook = ( - objects: Array>, - context: SavedObjectsExportContext +export type SavedObjectsExportTransform = ( + context: SavedObjectsExportTransformContext, + objects: Array> ) => SavedObject[] | Promise; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 5efe1ddd3d249..6f1994a19669c 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -44,8 +44,8 @@ export { SavedObjectsExportByObjectOptions, SavedObjectsExportResultDetails, SavedObjectsExportError, - SavedObjectsExportContext, - SavedObjectsTypeExportHook, + SavedObjectsExportTransformContext, + SavedObjectsExportTransform, } from './export'; export { diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 3d3005e625f53..1a920501541b6 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -72,7 +72,6 @@ const createSetupContractMock = () => { setClientFactoryProvider: jest.fn(), addClientWrapper: jest.fn(), registerType: jest.fn(), - registerExportHook: jest.fn(), }; return setupContract; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index dc27369a42740..8106dffda4210 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -49,7 +49,7 @@ import { import { Logger } from '../logging'; import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; import { SavedObjectsSerializer } from './serialization'; -import { SavedObjectsExporter, ISavedObjectsExporter, SavedObjectsTypeExportHook } from './export'; +import { SavedObjectsExporter, ISavedObjectsExporter } from './export'; import { SavedObjectsImporter, ISavedObjectsImporter } from './import'; import { registerRoutes } from './routes'; import { ServiceStatus } from '../status'; @@ -152,11 +152,6 @@ export interface SavedObjectsServiceSetup { * ``` */ registerType: (type: SavedObjectsType) => void; - - /** - * TODO: documentation - */ - registerExportHook: (type: string, exportHook: SavedObjectsTypeExportHook) => void; } /** @@ -286,7 +281,6 @@ export class SavedObjectsService private config?: SavedObjectConfig; private clientFactoryProvider?: SavedObjectsClientFactoryProvider; private clientFactoryWrappers: WrappedClientFactoryWrapper[] = []; - private exportHooks: Map = new Map(); private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); @@ -352,13 +346,6 @@ export class SavedObjectsService } this.typeRegistry.registerType(type); }, - registerExportHook: (type, hook) => { - if (this.started) { - throw new Error('cannot call `registerExportHook` after service startup.'); - } - // TODO - this.exportHooks.set(type, hook); - }, }; } @@ -472,7 +459,7 @@ export class SavedObjectsService createExporter: (savedObjectsClient) => new SavedObjectsExporter({ savedObjectsClient, - exportHooks: Object.fromEntries(this.exportHooks), + typeRegistry: this.typeRegistry, exportSizeLimit: this.config!.maxImportExportSize, }), createImporter: (savedObjectsClient) => diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index c8f8b47949ca5..f02b563caa7cd 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -20,6 +20,7 @@ import { SavedObjectsClient } from './service/saved_objects_client'; import { SavedObjectsTypeMappingDefinition } from './mappings'; import { SavedObjectMigrationMap } from './migrations'; +import { SavedObjectsExportTransform } from './export'; export { SavedObjectsImportResponse, @@ -292,4 +293,10 @@ export interface SavedObjectsTypeManagementDefinition { * {@link Capabilities | uiCapabilities} to check if the user has permission to access the object. */ getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; + /** + * An optional export transform function that can be used transform the objects of the registered type during + * the export process. This can be used to either mutates the exported objects, or add new objects + * to the export list. See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. + */ + onExport?: SavedObjectsExportTransform; } From 0ba9aea5f2aa9b7afe885a2bd364610656db8376 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 15 Jan 2021 13:43:27 +0100 Subject: [PATCH 04/18] add sorting for transforms --- .../export/apply_export_transforms.ts | 5 +- .../export/saved_objects_exporter.ts | 10 ++- .../server/saved_objects/export/utils.test.ts | 68 +++++++++++++++++++ src/core/server/saved_objects/export/utils.ts | 57 ++++++++++++++++ 4 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/core/server/saved_objects/export/utils.test.ts create mode 100644 src/core/server/saved_objects/export/utils.ts diff --git a/src/core/server/saved_objects/export/apply_export_transforms.ts b/src/core/server/saved_objects/export/apply_export_transforms.ts index 871865a5ef20e..c007ecfe3319a 100644 --- a/src/core/server/saved_objects/export/apply_export_transforms.ts +++ b/src/core/server/saved_objects/export/apply_export_transforms.ts @@ -21,12 +21,13 @@ import { SavedObject } from '../../../types'; import { KibanaRequest } from '../../http'; import { SavedObjectsExportError } from './errors'; import { SavedObjectsExportTransform, SavedObjectsExportTransformContext } from './types'; +import { getObjKey, SavedObjectComparator } from './utils'; interface ApplyExportTransformsOptions { objects: SavedObject[]; request: KibanaRequest; transforms: Record; - sortFunction?: (obj1: SavedObject, obj2: SavedObject) => number; + sortFunction?: SavedObjectComparator; } export const applyExportTransforms = async ({ @@ -87,8 +88,6 @@ const splitByType = (objects: SavedObject[]): Record => { }, {} as Record); }; -const getObjKey = (obj: SavedObject) => `${obj.type}|${obj.id}`; - const assertValidTransform = (transformedObjects: SavedObject[], initialKeys: string[]) => { const transformedKeys = transformedObjects.map(getObjKey); const missingKeys: string[] = []; diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.ts b/src/core/server/saved_objects/export/saved_objects_exporter.ts index cdcbe7b987976..0ea542599274d 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.ts @@ -32,6 +32,7 @@ import { } from './types'; import { SavedObjectsExportError } from './errors'; import { applyExportTransforms } from './apply_export_transforms'; +import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils'; /** * @public @@ -77,7 +78,7 @@ export class SavedObjectsExporter { */ public async exportByTypes(options: SavedObjectsExportByTypeOptions) { const objects = await this.fetchByTypes(options); - return this.processObjects(objects, { + return this.processObjects(objects, byIdAscComparator, { request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, @@ -97,7 +98,8 @@ export class SavedObjectsExporter { throw SavedObjectsExportError.exportSizeExceeded(this.#exportSizeLimit); } const objects = await this.fetchByObjects(options); - return this.processObjects(objects, { + const comparator = getPreservedOrderComparator(objects); + return this.processObjects(objects, comparator, { request: options.request, includeReferencesDeep: options.includeReferencesDeep, excludeExportDetails: options.excludeExportDetails, @@ -107,6 +109,7 @@ export class SavedObjectsExporter { private async processObjects( savedObjects: SavedObject[], + sortFunction: SavedObjectComparator, { request, excludeExportDetails = false, @@ -121,6 +124,7 @@ export class SavedObjectsExporter { request, objects: savedObjects, transforms: this.#exportTransforms, + sortFunction, }); if (includeReferencesDeep) { @@ -180,7 +184,7 @@ export class SavedObjectsExporter { findResponse.saved_objects // exclude the find-specific `score` property from the exported objects .map(({ score, ...obj }) => obj) - .sort((a: SavedObject, b: SavedObject) => (a.id > b.id ? 1 : -1)) + .sort(byIdAscComparator) ); } } diff --git a/src/core/server/saved_objects/export/utils.test.ts b/src/core/server/saved_objects/export/utils.test.ts new file mode 100644 index 0000000000000..82c904adde5b4 --- /dev/null +++ b/src/core/server/saved_objects/export/utils.test.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { byIdAscComparator, getPreservedOrderComparator } from './utils'; +import { SavedObject } from '../../../types'; + +const createObj = (id: string): SavedObject => ({ + id, + type: 'dummy', + attributes: {}, + references: [], +}); + +describe('byIdAscComparator', () => { + it('sorts the objects by id asc', () => { + const objs = [createObj('delta'), createObj('alpha'), createObj('beta')]; + + objs.sort(byIdAscComparator); + + expect(objs.map((obj) => obj.id)).toEqual(['alpha', 'beta', 'delta']); + }); +}); + +describe('getPreservedOrderComparator', () => { + it('sorts objects depending on the order of the provided list', () => { + const objA = createObj('A'); + const objB = createObj('B'); + const objC = createObj('C'); + + const comparator = getPreservedOrderComparator([objA, objB, objC]); + + const objs = [objC, objA, objB]; + objs.sort(comparator); + + expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C']); + }); + + it('appends unknown objects at the end of the list and sort them by id', () => { + const objA = createObj('A'); + const objB = createObj('B'); + const objC = createObj('C'); + const addedA = createObj('addedA'); + const addedB = createObj('addedB'); + + const comparator = getPreservedOrderComparator([objA, objB, objC]); + + const objs = [addedB, objC, addedA, objA, objB]; + objs.sort(comparator); + + expect(objs.map((obj) => obj.id)).toEqual(['A', 'B', 'C', 'addedA', 'addedB']); + }); +}); diff --git a/src/core/server/saved_objects/export/utils.ts b/src/core/server/saved_objects/export/utils.ts new file mode 100644 index 0000000000000..2f9235086f899 --- /dev/null +++ b/src/core/server/saved_objects/export/utils.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObject } from '../../../types'; + +export type SavedObjectComparator = (a: SavedObject, b: SavedObject) => number; + +export const getObjKey = (obj: SavedObject) => `${obj.type}|${obj.id}`; + +export const byIdAscComparator: SavedObjectComparator = (a: SavedObject, b: SavedObject) => + a.id > b.id ? 1 : -1; + +/** + * Create a comparator that will sort objects depending on their position in the provided array. + * Objects not present in the array will be appended at the end of the list, and sorted by id asc. + * + * @example + * ```ts + * const comparator = getPreservedOrderComparator([objA, objB, objC]); + * const list = [newB, objB, objC, newA, objA]; // with obj.title matching their variable name + * list.sort() + * // list = [objA, objB, objC, newA, newB] + * ``` + */ +export const getPreservedOrderComparator = (objects: SavedObject[]): SavedObjectComparator => { + const orderedKeys = objects.map(getObjKey); + return (a: SavedObject, b: SavedObject) => { + const indexA = orderedKeys.indexOf(getObjKey(a)); + const indexB = orderedKeys.indexOf(getObjKey(b)); + if (indexA > -1 && indexB > -1) { + return indexA - indexB > 0 ? 1 : -1; + } + if (indexA > -1) { + return -1; + } + if (indexB > -1) { + return 1; + } + return byIdAscComparator(a, b); + }; +}; From 067c3f70db1b90ad10eb8ffbb39b8d41b0898209 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 08:29:49 +0100 Subject: [PATCH 05/18] add type validation in SOTR --- .../saved_objects_type_registry.test.ts | 75 ++++++++++++++----- .../saved_objects_type_registry.ts | 11 +++ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/core/server/saved_objects/saved_objects_type_registry.test.ts b/src/core/server/saved_objects/saved_objects_type_registry.test.ts index 25c94324c8f01..e6f7ba2b69c44 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.test.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.test.ts @@ -36,25 +36,66 @@ describe('SavedObjectTypeRegistry', () => { registry = new SavedObjectTypeRegistry(); }); - it('allows to register types', () => { - registry.registerType(createType({ name: 'typeA' })); - registry.registerType(createType({ name: 'typeB' })); - registry.registerType(createType({ name: 'typeC' })); - - expect( - registry - .getAllTypes() - .map((type) => type.name) - .sort() - ).toEqual(['typeA', 'typeB', 'typeC']); - }); + describe('#registerType', () => { + it('allows to register types', () => { + registry.registerType(createType({ name: 'typeA' })); + registry.registerType(createType({ name: 'typeB' })); + registry.registerType(createType({ name: 'typeC' })); + + expect( + registry + .getAllTypes() + .map((type) => type.name) + .sort() + ).toEqual(['typeA', 'typeB', 'typeC']); + }); - it('throws when trying to register the same type twice', () => { - registry.registerType(createType({ name: 'typeA' })); - registry.registerType(createType({ name: 'typeB' })); - expect(() => { + it('throws when trying to register the same type twice', () => { registry.registerType(createType({ name: 'typeA' })); - }).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`); + registry.registerType(createType({ name: 'typeB' })); + expect(() => { + registry.registerType(createType({ name: 'typeA' })); + }).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`); + }); + + it('throws when `management.onExport` is specified but `management.importableAndExportable` is not or false', () => { + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + onExport: (ctx, objs) => objs, + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"` + ); + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + importableAndExportable: false, + onExport: (ctx, objs) => objs, + }, + }) + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'"` + ); + expect(() => { + registry.registerType( + createType({ + name: 'typeA', + management: { + importableAndExportable: true, + onExport: (ctx, objs) => objs, + }, + }) + ); + }).not.toThrow(); + }); }); describe('#getType', () => { diff --git a/src/core/server/saved_objects/saved_objects_type_registry.ts b/src/core/server/saved_objects/saved_objects_type_registry.ts index bb840e459bf22..09b412b793c2d 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.ts @@ -43,6 +43,7 @@ export class SavedObjectTypeRegistry { if (this.types.has(type.name)) { throw new Error(`Type '${type.name}' is already registered`); } + validateType(type); this.types.set(type.name, deepFreeze(type)); } @@ -127,3 +128,13 @@ export class SavedObjectTypeRegistry { return this.types.get(type)?.management?.importableAndExportable ?? false; } } + +const validateType = ({ name, management }: SavedObjectsType) => { + if (management) { + if (management.onExport && !management.importableAndExportable) { + throw new Error( + `Type ${name}: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'` + ); + } + } +}; From cd4ac171aa881243dac58650484ec9e7a34f70a5 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 13:12:11 +0100 Subject: [PATCH 06/18] add FTR tests --- .../export_transform/data.json | 115 ++++ .../export_transform/mappings.json | 489 ++++++++++++++++++ test/plugin_functional/config.ts | 1 + .../saved_object_export_hooks/kibana.json | 8 + .../saved_object_export_hooks/package.json | 14 + .../saved_object_export_hooks/server/index.ts | 22 + .../server/plugin.ts | 107 ++++ .../saved_object_export_hooks/tsconfig.json | 16 + .../export_transform.ts | 109 ++++ .../saved_objects_management/index.ts | 25 + 10 files changed, 906 insertions(+) create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json create mode 100644 test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json create mode 100644 test/plugin_functional/plugins/saved_object_export_hooks/kibana.json create mode 100644 test/plugin_functional/plugins/saved_object_export_hooks/package.json create mode 100644 test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts create mode 100644 test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts create mode 100644 test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json create mode 100644 test/plugin_functional/test_suites/saved_objects_management/export_transform.ts create mode 100644 test/plugin_functional/test_suites/saved_objects_management/index.ts diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json new file mode 100644 index 0000000000000..f5c8bd4f51c59 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json @@ -0,0 +1,115 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform:type_1-obj_1", + "source": { + "test-export-transform": { + "title": "test_1-obj_1", + "enabled": true + }, + "type": "test-export-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform:type_1-obj_2", + "source": { + "test-export-transform": { + "title": "test_1-obj_2", + "enabled": true + }, + "type": "test-export-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add:type_2-obj_1", + "source": { + "test-export-add": { + "title": "test_2-obj_1" + }, + "type": "test-export-add", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add:type_2-obj_2", + "source": { + "test-export-add": { + "title": "test_2-obj_2" + }, + "type": "test-export-add", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add-dep:type_dep-obj_1", + "source": { + "test-export-add-dep": { + "title": "type_dep-obj_1" + }, + "type": "test-export-add-dep", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-export-add", + "id": "type_2-obj_1" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-add-dep:type_dep-obj_2", + "source": { + "test-export-add-dep": { + "title": "type_dep-obj_2" + }, + "type": "test-export-add-dep", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z", + "references": [ + { + "type": "test-export-add", + "id": "type_2-obj_2" + } + ] + } + } +} diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json new file mode 100644 index 0000000000000..1bd2dd0ba24b3 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -0,0 +1,489 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "test-export-transform": { + "properties": { + "title": { "type": "text" }, + "enabled": { "type": "boolean" } + } + }, + "test-export-add": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-add-dep": { + "properties": { + "title": { "type": "text" } + } + }, + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "go": { + "type": "long", + "null_value": 0 + }, + "java": { + "type": "long", + "null_value": 0 + }, + "js-base": { + "type": "long", + "null_value": 0 + }, + "nodejs": { + "type": "long", + "null_value": 0 + }, + "python": { + "type": "long", + "null_value": 0 + }, + "ruby": { + "type": "long", + "null_value": 0 + } + } + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "id": { + "type": "text", + "index": false + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "telemetry:optIn": { + "type": "boolean" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape", + "tree": "quadtree" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "space": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } +} diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index 9ec82e291e537..23a55e493d0ca 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -39,6 +39,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./test_suites/doc_views'), require.resolve('./test_suites/application_links'), require.resolve('./test_suites/data_plugin'), + require.resolve('./test_suites/saved_objects_management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/kibana.json b/test/plugin_functional/plugins/saved_object_export_hooks/kibana.json new file mode 100644 index 0000000000000..a1af7495a122c --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_hooks/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "savedObjectExportHooks", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["saved_object_export_hooks"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/package.json b/test/plugin_functional/plugins/saved_object_export_hooks/package.json new file mode 100644 index 0000000000000..fed568977c903 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_hooks/package.json @@ -0,0 +1,14 @@ +{ + "name": "saved_object_export_hooks", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/saved_object_export_hooks", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts new file mode 100644 index 0000000000000..1ce2cde5b4961 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectExportHooksPlugin } from './plugin'; + +export const plugin = () => new SavedObjectExportHooksPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts new file mode 100644 index 0000000000000..1fc67efcbc281 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts @@ -0,0 +1,107 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup } from 'kibana/server'; + +export class SavedObjectExportHooksPlugin implements Plugin { + public setup({ savedObjects, getStartServices }: CoreSetup, deps: {}) { + const savedObjectStartContractPromise = getStartServices().then( + ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + ); + + // example of a SO type that will mutates its properties + // during the export transform + savedObjects.registerType({ + name: 'test-export-transform', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + enabled: { + type: 'boolean', + }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + }, + })); + }, + }, + }); + + // example of a SO type that will add additional objects + // to the export during the export transform + savedObjects.registerType({ + name: 'test-export-add', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: async (ctx, objs) => { + const { getScopedClient } = await savedObjectStartContractPromise; + const client = getScopedClient(ctx.request); + const objRefs = objs.map(({ id, type }) => ({ id, type })); + const depResponse = await client.find({ + type: 'test-export-add-dep', + hasReference: objRefs, + }); + return [...objs, ...depResponse.saved_objects]; + }, + }, + }); + + // dependency of `test_export_transform_2` that will be included + // when exporting them + savedObjects.registerType({ + name: 'test-export-add-dep', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json b/test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json new file mode 100644 index 0000000000000..da457c9ba32fc --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts new file mode 100644 index 0000000000000..f4c922cf5b1c2 --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts @@ -0,0 +1,109 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import type { SavedObject } from '../../../../src/core/types'; +import { PluginFunctionalProviderContext } from '../../services'; + +function parseNdJson(input: string): Array> { + return input.split('\n').map((str) => JSON.parse(str)); +} + +export default function ({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('export transforms', () => { + before(async () => { + await esArchiver.load( + '../functional/fixtures/es_archiver/saved_objects_management/export_transform' + ); + }); + + after(async () => { + await esArchiver.unload( + '../functional/fixtures/es_archiver/saved_objects_management/export_transform' + ); + }); + + it('allows to mutate the objects during an export', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-transform'], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ + { + id: 'type_1-obj_1', + enabled: false, + }, + { + id: 'type_1-obj_2', + enabled: false, + }, + ]); + }); + }); + + it('allows to add additional objects to an export', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + objects: [ + { + type: 'test-export-add', + id: 'type_2-obj_1', + }, + ], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); + }); + }); + + it('allows to add additional objects to an export when exporting by type', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-add'], + excludeExportDetails: true, + }) + .expect(200) + .then((resp) => { + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_2-obj_1', + 'type_2-obj_2', + 'type_dep-obj_1', + 'type_dep-obj_2', + ]); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/index.ts b/test/plugin_functional/test_suites/saved_objects_management/index.ts new file mode 100644 index 0000000000000..3fd6f5f494fed --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ loadTestFile }: PluginFunctionalProviderContext) { + describe('Saved Objects Management', function () { + loadTestFile(require.resolve('./export_transform')); + }); +} From f36534f242a56d897ac72d25dfdc2a51bae9d793 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 13:40:29 +0100 Subject: [PATCH 07/18] update documentation --- src/core/server/saved_objects/export/types.ts | 69 ++++++++++++++++++- src/core/server/saved_objects/types.ts | 7 +- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index 2a48d05d25f56..e6bf8cc1be855 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -85,11 +85,78 @@ export interface SavedObjectsExportResultDetails { * @public */ export interface SavedObjectsExportTransformContext { + /** + * The request that initiated the export request. Can be used to create scoped + * services or client inside the {@link SavedObjectsExportTransform | transformation} + */ request: KibanaRequest; } /** - * TODO: doc + examples + * Transformation function used to mutate the exported objects of the associated type. + * + * @example + * Registering a transform function changing the object's attributes during the export + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * onExport: (ctx, objects) => { + * return objects.map((obj) => ({ + * ...obj, + * attributes: { + * ...obj.attributes, + * enabled: false, + * } + * }) + * } + * }, + * }); + * } + * } + * ``` + * + * @example + * Registering a transform function adding additional objects to the export + * ```ts + * // src/plugins/my_plugin/server/plugin.ts + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * const savedObjectStartContractPromise = getStartServices().then( + * ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + * ); + * + * core.savedObjects.registerType({ + * ...myType, + * management: { + * ...myType.management, + * onExport: async (ctx, objects) => { + * const { getScopedClient } = await savedObjectStartContractPromise; + * const client = getScopedClient(ctx.request); + * + * const depResponse = await client.find({ + * type: 'my-nested-object', + * hasReference: objs.map(({ id, type }) => ({ id, type })), + * }); + * + * return [...objs, ...depResponse.saved_objects]; + * } + * }, + * }); + * } + * } + * ``` + * + * @remarks Trying to change an object's id or type during the transform will result in + * a runtime error during the export process. * * @public */ diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index f02b563caa7cd..6c26b617e26f8 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -295,8 +295,11 @@ export interface SavedObjectsTypeManagementDefinition { getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; /** * An optional export transform function that can be used transform the objects of the registered type during - * the export process. This can be used to either mutates the exported objects, or add new objects - * to the export list. See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. + * the export process. + * + * It can be used to either mutates the exported objects, or add new objects to the export list. + * + * See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. */ onExport?: SavedObjectsExportTransform; } From df1eb4dd2d658518d4de0820030e8f5d8676b7bf Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 13:55:08 +0100 Subject: [PATCH 08/18] add explicit so type export for client-side --- src/core/server/types.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 48b3a9058605c..3d666b8e10d76 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -19,7 +19,31 @@ /** This module is intended for consumption by public to avoid import issues with server-side code */ export { PluginOpaqueId } from './plugins/types'; -export * from './saved_objects/types'; +export type { + SavedObjectsImportResponse, + SavedObjectsImportSuccess, + SavedObjectsImportConflictError, + SavedObjectsImportAmbiguousConflictError, + SavedObjectsImportUnsupportedTypeError, + SavedObjectsImportMissingReferencesError, + SavedObjectsImportUnknownError, + SavedObjectsImportFailure, + SavedObjectsImportRetry, + SavedObjectAttributes, + SavedObjectAttribute, + SavedObjectAttributeSingle, + SavedObject, + SavedObjectError, + SavedObjectReference, + SavedObjectsMigrationVersion, + SavedObjectStatusMeta, + SavedObjectsFindOptionsReference, + SavedObjectsFindOptions, + SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, + SavedObjectsClientContract, + SavedObjectsNamespaceType, +} from './saved_objects/types'; export * from './ui_settings/types'; export * from './legacy/types'; export type { EnvironmentMode, PackageInfo } from '@kbn/config'; From 3887dbdbcd5ec90839a6012e17aa9a6985c0a9b3 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 14:22:50 +0100 Subject: [PATCH 09/18] update generated doc --- .../core/server/kibana-plugin-core-server.md | 2 + ...ore-server.savedobjectexportbaseoptions.md | 1 + ...er.savedobjectexportbaseoptions.request.md | 13 +++ ...rver.savedobjectsexporter._constructor_.md | 5 +- ...plugin-core-server.savedobjectsexporter.md | 2 +- ...bjectsexporterror.invalidtransformerror.md | 24 ++++++ ...gin-core-server.savedobjectsexporterror.md | 2 + ...objectsexporterror.objecttransformerror.md | 25 ++++++ ...core-server.savedobjectsexporttransform.md | 84 +++++++++++++++++++ ...rver.savedobjectsexporttransformcontext.md | 20 +++++ ...edobjectsexporttransformcontext.request.md | 13 +++ ...er.savedobjectstypemanagementdefinition.md | 1 + ...bjectstypemanagementdefinition.onexport.md | 17 ++++ .../kibana-plugin-plugins-data-server.md | 2 +- ...ugin-plugins-data-server.sessionservice.md | 2 +- src/core/public/public.api.md | 7 ++ src/core/server/server.api.md | 15 +++- src/plugins/data/public/public.api.md | 8 ++ src/plugins/embeddable/public/public.api.md | 8 ++ 19 files changed, 245 insertions(+), 6 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 06c7983f89a78..40d112cf69430 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -164,6 +164,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsExportByObjectOptions](./kibana-plugin-core-server.savedobjectsexportbyobjectoptions.md) | Options for the [export by objects API](./kibana-plugin-core-server.savedobjectsexporter.exportbyobjects.md) | | [SavedObjectsExportByTypeOptions](./kibana-plugin-core-server.savedobjectsexportbytypeoptions.md) | Options for the [export by type API](./kibana-plugin-core-server.savedobjectsexporter.exportbytypes.md) | | [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | +| [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) | Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) | | [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) | | | [SavedObjectsFindOptionsReference](./kibana-plugin-core-server.savedobjectsfindoptionsreference.md) | | | [SavedObjectsFindResponse](./kibana-plugin-core-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | @@ -294,6 +295,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [SavedObjectsNamespaceType](./kibana-plugin-core-server.savedobjectsnamespacetype.md) | The namespace type dictates how a saved object can be interacted in relation to namespaces. Each type is mutually exclusive: \* single (default): this type of saved object is namespace-isolated, e.g., it exists in only one namespace. \* multiple: this type of saved object is shareable, e.g., it can exist in one or more namespaces. \* agnostic: this type of saved object is global. | | [SavedObjectUnsanitizedDoc](./kibana-plugin-core-server.savedobjectunsanitizeddoc.md) | Describes Saved Object documents from Kibana < 7.0.0 which don't have a references root property defined. This type should only be used in migrations. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md index eb35bb6a4ea5c..0e8fa73039d40 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.md @@ -18,4 +18,5 @@ export interface SavedObjectExportBaseOptions | [excludeExportDetails](./kibana-plugin-core-server.savedobjectexportbaseoptions.excludeexportdetails.md) | boolean | flag to not append [export details](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) to the end of the export stream. | | [includeReferencesDeep](./kibana-plugin-core-server.savedobjectexportbaseoptions.includereferencesdeep.md) | boolean | flag to also include all related saved objects in the export stream. | | [namespace](./kibana-plugin-core-server.savedobjectexportbaseoptions.namespace.md) | string | optional namespace to override the namespace used by the savedObjectsClient. | +| [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md) | KibanaRequest | The http request initiating the export. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md new file mode 100644 index 0000000000000..d425f9b88e818 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectexportbaseoptions.request.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectExportBaseOptions](./kibana-plugin-core-server.savedobjectexportbaseoptions.md) > [request](./kibana-plugin-core-server.savedobjectexportbaseoptions.request.md) + +## SavedObjectExportBaseOptions.request property + +The http request initiating the export. + +Signature: + +```typescript +request: KibanaRequest; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md index cc192b03ca7c2..5e959bbee7beb 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter._constructor_.md @@ -9,8 +9,9 @@ Constructs a new instance of the `SavedObjectsExporter` class Signature: ```typescript -constructor({ savedObjectsClient, exportSizeLimit, }: { +constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }); ``` @@ -19,5 +20,5 @@ constructor({ savedObjectsClient, exportSizeLimit, }: { | Parameter | Type | Description | | --- | --- | --- | -| { savedObjectsClient, exportSizeLimit, } | {
savedObjectsClient: SavedObjectsClientContract;
exportSizeLimit: number;
} | | +| { savedObjectsClient, typeRegistry, exportSizeLimit, } | {
savedObjectsClient: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
exportSizeLimit: number;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md index d8d9248f34af6..727108b824c84 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporter.md @@ -15,7 +15,7 @@ export declare class SavedObjectsExporter | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)({ savedObjectsClient, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the SavedObjectsExporter class | +| [(constructor)({ savedObjectsClient, typeRegistry, exportSizeLimit, })](./kibana-plugin-core-server.savedobjectsexporter._constructor_.md) | | Constructs a new instance of the SavedObjectsExporter class | ## Properties diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md new file mode 100644 index 0000000000000..5a390bd450421 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [invalidTransformError](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md) + +## SavedObjectsExportError.invalidTransformError() method + +Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id. + +Signature: + +```typescript +static invalidTransformError(objectKeys: string[]): SavedObjectsExportError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objectKeys | string[] | | + +Returns: + +`SavedObjectsExportError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md index bfeaa03a94700..7d5c6e5d89a5b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.md @@ -29,5 +29,7 @@ export declare class SavedObjectsExportError extends Error | Method | Modifiers | Description | | --- | --- | --- | | [exportSizeExceeded(limit)](./kibana-plugin-core-server.savedobjectsexporterror.exportsizeexceeded.md) | static | | +| [invalidTransformError(objectKeys)](./kibana-plugin-core-server.savedobjectsexporterror.invalidtransformerror.md) | static | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) performed an invalid operation during the transform, such as removing objects from the export, or changing an object's type or id. | | [objectFetchError(objects)](./kibana-plugin-core-server.savedobjectsexporterror.objectfetcherror.md) | static | | +| [objectTransformError(objects, cause)](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md) | static | Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md new file mode 100644 index 0000000000000..4463e9ff06da0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportError](./kibana-plugin-core-server.savedobjectsexporterror.md) > [objectTransformError](./kibana-plugin-core-server.savedobjectsexporterror.objecttransformerror.md) + +## SavedObjectsExportError.objectTransformError() method + +Error returned when a [export tranform](./kibana-plugin-core-server.savedobjectsexporttransform.md) threw an error + +Signature: + +```typescript +static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObject[] | | +| cause | Error | | + +Returns: + +`SavedObjectsExportError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md new file mode 100644 index 0000000000000..14410e490b497 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md @@ -0,0 +1,84 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +## SavedObjectsExportTransform type + +Transformation function used to mutate the exported objects of the associated type. + +Signature: + +```typescript +export declare type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; +``` + +## Remarks + +Trying to change an object's id or type during the transform will result in a runtime error during the export process. + +## Example 1 + +Registering a transform function changing the object's attributes during the export + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + onExport: (ctx, objects) => { + return objects.map((obj) => ({ + ...obj, + attributes: { + ...obj.attributes, + enabled: false, + } + }) + } + }, + }); + } +} + +``` + +## Example 2 + +Registering a transform function adding additional objects to the export + +```ts +// src/plugins/my_plugin/server/plugin.ts +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + const savedObjectStartContractPromise = getStartServices().then( + ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart + ); + + core.savedObjects.registerType({ + ...myType, + management: { + ...myType.management, + onExport: async (ctx, objects) => { + const { getScopedClient } = await savedObjectStartContractPromise; + const client = getScopedClient(ctx.request); + + const depResponse = await client.find({ + type: 'my-nested-object', + hasReference: objs.map(({ id, type }) => ({ id, type })), + }); + + return [...objs, ...depResponse.saved_objects]; + } + }, + }); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md new file mode 100644 index 0000000000000..271f0048842b2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) + +## SavedObjectsExportTransformContext interface + +Context passed down to a [export transform function](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +Signature: + +```typescript +export interface SavedObjectsExportTransformContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md) | KibanaRequest | The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md) | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md new file mode 100644 index 0000000000000..fe04698899c7c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsExportTransformContext](./kibana-plugin-core-server.savedobjectsexporttransformcontext.md) > [request](./kibana-plugin-core-server.savedobjectsexporttransformcontext.request.md) + +## SavedObjectsExportTransformContext.request property + +The request that initiated the export request. Can be used to create scoped services or client inside the [transformation](./kibana-plugin-core-server.savedobjectsexporttransform.md) + +Signature: + +```typescript +request: KibanaRequest; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index 9d87e51767caa..b8b9ab68ed96c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -22,4 +22,5 @@ export interface SavedObjectsTypeManagementDefinition | [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | +| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutates the exported objects, or add new objects to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md new file mode 100644 index 0000000000000..44e211fa77e75 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) + +## SavedObjectsTypeManagementDefinition.onExport property + +An optional export transform function that can be used transform the objects of the registered type during the export process. + +It can be used to either mutates the exported objects, or add new objects to the export list. + +See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. + +Signature: + +```typescript +onExport?: SavedObjectsExportTransform; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 75227575038ab..1b4cf5585cb3e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -14,7 +14,7 @@ | [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | -| [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) | The OSS session service. See data\_enhanced in X-Pack for the background session service. | +| [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) | The OSS session service. See data\_enhanced in X-Pack for the search session service. | ## Enumerations diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md index 2369b00644548..2457f7103d8fa 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md @@ -4,7 +4,7 @@ ## SessionService class -The OSS session service. See data\_enhanced in X-Pack for the background session service. +The OSS session service. See data\_enhanced in X-Pack for the search session service. Signature: diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 2f4c871c33431..4890cd1dba090 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -9,6 +9,7 @@ import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigPath } from '@kbn/config'; +import { DetailedPeerCertificate } from 'tls'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; @@ -18,20 +19,25 @@ import { EuiGlobalToastListToast } from '@elastic/eui'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; +import { SchemaTypeError } from '@kbn/config-schema'; import { ShallowPromise } from '@kbn/utility-types'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; @@ -40,6 +46,7 @@ import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiCounterMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @internal (undocumented) diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 75f1580ceba8e..312b15e61b48a 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2064,6 +2064,7 @@ export interface SavedObjectExportBaseOptions { excludeExportDetails?: boolean; includeReferencesDeep?: boolean; namespace?: string; + request: KibanaRequest; } // @public @@ -2385,8 +2386,9 @@ export interface SavedObjectsExportByTypeOptions extends SavedObjectExportBaseOp export class SavedObjectsExporter { // (undocumented) #private; - constructor({ savedObjectsClient, exportSizeLimit, }: { + constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }); exportByObjects(options: SavedObjectsExportByObjectOptions): Promise; @@ -2400,8 +2402,10 @@ export class SavedObjectsExportError extends Error { readonly attributes?: Record | undefined; // (undocumented) static exportSizeExceeded(limit: number): SavedObjectsExportError; + static invalidTransformError(objectKeys: string[]): SavedObjectsExportError; // (undocumented) static objectFetchError(objects: SavedObject[]): SavedObjectsExportError; + static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError; // (undocumented) readonly type: string; } @@ -2416,6 +2420,14 @@ export interface SavedObjectsExportResultDetails { }>; } +// @public +export type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; + +// @public +export interface SavedObjectsExportTransformContext { + request: KibanaRequest; +} + // @public export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping; @@ -2790,6 +2802,7 @@ export interface SavedObjectsTypeManagementDefinition { getTitle?: (savedObject: SavedObject) => string; icon?: string; importableAndExportable?: boolean; + onExport?: SavedObjectsExportTransform; } // @public diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 50b6b2223bd0a..289e118d54bd4 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -24,6 +24,7 @@ import * as CSS from 'csstype'; import { Datatable as Datatable_2 } from 'src/plugins/expressions'; import { Datatable as Datatable_3 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; +import { DetailedPeerCertificate } from 'tls'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; @@ -45,6 +46,7 @@ import { History } from 'history'; import { Href } from 'history'; import { HttpSetup } from 'kibana/public'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { InjectedIntl } from '@kbn/i18n/react'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; @@ -60,9 +62,11 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { Moment } from 'moment'; import moment from 'moment'; import { NameList } from 'elasticsearch'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; @@ -75,6 +79,7 @@ import React from 'react'; import * as React_3 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Reporter } from '@kbn/analytics'; +import { Request as Request_2 } from '@hapi/hapi'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; @@ -85,6 +90,7 @@ import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; import { SavedObjectsFindOptions } from 'kibana/public'; import { SavedObjectsFindResponse } from 'kibana/server'; +import { SchemaTypeError } from '@kbn/config-schema'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -94,12 +100,14 @@ import { ToastsSetup } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { UiCounterMetricType } from '@kbn/analytics'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_GLOBAL_APPLY_FILTER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 386f1b369bef8..3703de6a0e1a6 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -12,6 +12,7 @@ import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import * as CSS from 'csstype'; +import { DetailedPeerCertificate } from 'tls'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -25,6 +26,7 @@ import { History } from 'history'; import { Href } from 'history'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; @@ -32,31 +34,37 @@ import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; import { OverlayRef as OverlayRef_2 } from 'src/core/public'; import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PluginInitializerContext } from 'src/core/public'; import * as PropTypes from 'prop-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; +import { SchemaTypeError } from '@kbn/config-schema'; import { ShallowPromise } from '@kbn/utility-types'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_ADD_PANEL" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) From 417aff97a57b60eeedfc75bc7a64e12ee9fe154c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 18 Jan 2021 14:41:55 +0100 Subject: [PATCH 10/18] add exporter test --- .../export/saved_objects_exporter.test.ts | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts index cbdf7d98a841a..5226eb5e9aa41 100644 --- a/src/core/server/saved_objects/export/saved_objects_exporter.test.ts +++ b/src/core/server/saved_objects/export/saved_objects_exporter.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import type { SavedObject } from '../../../types'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; import { SavedObjectTypeRegistry } from '../saved_objects_type_registry'; @@ -24,7 +25,7 @@ import { httpServerMock } from '../../http/http_server.mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; -async function readStreamToCompletion(stream: Readable) { +async function readStreamToCompletion(stream: Readable): Promise>> { return createPromiseFromStreams([stream, createConcatStream([])]); } @@ -132,6 +133,52 @@ describe('getSortedObjectsForExport()', () => { `); }); + test('applies the export transforms', async () => { + typeRegistry.registerType({ + name: 'foo', + mappings: { properties: {} }, + namespaceType: 'single', + hidden: false, + management: { + importableAndExportable: true, + onExport: (ctx, objects) => { + objects.forEach((obj: SavedObject) => { + obj.attributes.foo = 'modified'; + }); + return objects; + }, + }, + }); + exporter = new SavedObjectsExporter({ savedObjectsClient, exportSizeLimit, typeRegistry }); + + savedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + saved_objects: [ + { + id: '1', + type: 'foo', + attributes: { + foo: 'initial', + }, + score: 0, + references: [], + }, + ], + per_page: 1, + page: 0, + }); + const exportStream = await exporter.exportByTypes({ + request, + types: ['foo'], + excludeExportDetails: true, + }); + + const response = await readStreamToCompletion(exportStream); + + expect(response).toHaveLength(1); + expect(response[0].attributes.foo).toEqual('modified'); + }); + test('omits the `namespaces` property from the export', async () => { savedObjectsClient.find.mockResolvedValueOnce({ total: 2, From 5434e0f1dc00ec6c2ed0f2f6b5a13177b6ed9443 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 20 Jan 2021 08:08:55 +0100 Subject: [PATCH 11/18] update license headers --- .../export/apply_export_transforms.test.ts | 21 +++++------------- .../export/apply_export_transforms.ts | 21 +++++------------- .../server/saved_objects/export/utils.test.ts | 21 +++++------------- src/core/server/saved_objects/export/utils.ts | 21 +++++------------- .../saved_object_export_hooks/server/index.ts | 21 +++++------------- .../server/plugin.ts | 21 +++++------------- .../export_transform.ts | 21 +++++------------- .../saved_objects_management/index.ts | 22 +++++-------------- 8 files changed, 41 insertions(+), 128 deletions(-) diff --git a/src/core/server/saved_objects/export/apply_export_transforms.test.ts b/src/core/server/saved_objects/export/apply_export_transforms.test.ts index b27d1e747f57f..b1d0a35162524 100644 --- a/src/core/server/saved_objects/export/apply_export_transforms.test.ts +++ b/src/core/server/saved_objects/export/apply_export_transforms.test.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { SavedObject } from '../../../types'; diff --git a/src/core/server/saved_objects/export/apply_export_transforms.ts b/src/core/server/saved_objects/export/apply_export_transforms.ts index c007ecfe3319a..0297fe201ef61 100644 --- a/src/core/server/saved_objects/export/apply_export_transforms.ts +++ b/src/core/server/saved_objects/export/apply_export_transforms.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { SavedObject } from '../../../types'; diff --git a/src/core/server/saved_objects/export/utils.test.ts b/src/core/server/saved_objects/export/utils.test.ts index 82c904adde5b4..c547aa2271cf0 100644 --- a/src/core/server/saved_objects/export/utils.test.ts +++ b/src/core/server/saved_objects/export/utils.test.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { byIdAscComparator, getPreservedOrderComparator } from './utils'; diff --git a/src/core/server/saved_objects/export/utils.ts b/src/core/server/saved_objects/export/utils.ts index 2f9235086f899..e8567c6da1dca 100644 --- a/src/core/server/saved_objects/export/utils.ts +++ b/src/core/server/saved_objects/export/utils.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { SavedObject } from '../../../types'; diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts index 1ce2cde5b4961..fdd06c996ffb4 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts +++ b/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { SavedObjectExportHooksPlugin } from './plugin'; diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts index 1fc67efcbc281..9e4315c5d378f 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import { Plugin, CoreSetup } from 'kibana/server'; diff --git a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts index f4c922cf5b1c2..7996bc48e5aa9 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts @@ -1,20 +1,9 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import expect from '@kbn/expect'; diff --git a/test/plugin_functional/test_suites/saved_objects_management/index.ts b/test/plugin_functional/test_suites/saved_objects_management/index.ts index 3fd6f5f494fed..97bc053ed5877 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/index.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/index.ts @@ -1,21 +1,11 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ + import { PluginFunctionalProviderContext } from '../../services'; export default function ({ loadTestFile }: PluginFunctionalProviderContext) { From 9cee8319f391af3a5996d9f1e0a1504e5c3045ee Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 20 Jan 2021 08:20:14 +0100 Subject: [PATCH 12/18] update generated doc --- src/core/public/public.api.md | 7 +++++++ src/plugins/embeddable/public/public.api.md | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 5c0b2a45abd46..cf8ae325870eb 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -9,6 +9,7 @@ import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigPath } from '@kbn/config'; +import { DetailedPeerCertificate } from 'tls'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; @@ -18,20 +19,25 @@ import { EuiGlobalToastListToast } from '@elastic/eui'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; +import { SchemaTypeError } from '@kbn/config-schema'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; @@ -39,6 +45,7 @@ import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiCounterMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @internal (undocumented) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index f41d92dfb65a4..2f9b43121b45a 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -12,6 +12,7 @@ import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; import * as CSS from 'csstype'; +import { DetailedPeerCertificate } from 'tls'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -25,6 +26,7 @@ import { History } from 'history'; import { Href } from 'history'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; +import { IncomingHttpHeaders } from 'http'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; @@ -32,30 +34,36 @@ import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; +import { ObjectType } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; import { OverlayRef as OverlayRef_2 } from 'src/core/public'; import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; +import { PeerCertificate } from 'tls'; import { PluginInitializerContext } from 'src/core/public'; import * as PropTypes from 'prop-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; +import { Request } from '@hapi/hapi'; import * as Rx from 'rxjs'; import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; +import { SchemaTypeError } from '@kbn/config-schema'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; import { UnregisterCallback } from 'history'; +import { URL } from 'url'; import { UserProvidedValues } from 'src/core/server/types'; // Warning: (ae-missing-release-tag) "ACTION_ADD_PANEL" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) From 4f292e91d96d8e593876850fca6eb799bca665bd Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 09:33:11 +0100 Subject: [PATCH 13/18] fix so import... imports --- src/core/server/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 5fcd0e3e51ae3..74f9fb65db54d 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -18,6 +18,9 @@ export type { SavedObjectsImportUnknownError, SavedObjectsImportFailure, SavedObjectsImportRetry, + SavedObjectsImportWarning, + SavedObjectsImportActionRequiredWarning, + SavedObjectsImportSimpleWarning, SavedObjectAttributes, SavedObjectAttribute, SavedObjectAttributeSingle, From 9ac648f5131c1f91e33a507bce407790b3920e81 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 09:38:09 +0100 Subject: [PATCH 14/18] update generated doc --- ...server.savedobjectstypemanagementdefinition.md | 1 + src/core/server/server.api.md | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index 92b6ddf29b8ec..cded5626d5ff1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -22,5 +22,6 @@ export interface SavedObjectsTypeManagementDefinition | [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | +| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutates the exported objects, or add new objects to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | | [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d0ba6aa1900c7..a63ecad7c9180 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2079,6 +2079,7 @@ export interface SavedObjectExportBaseOptions { excludeExportDetails?: boolean; includeReferencesDeep?: boolean; namespace?: string; + request: KibanaRequest; } // @public @@ -2403,8 +2404,9 @@ export interface SavedObjectsExportByTypeOptions extends SavedObjectExportBaseOp export class SavedObjectsExporter { // (undocumented) #private; - constructor({ savedObjectsClient, exportSizeLimit, }: { + constructor({ savedObjectsClient, typeRegistry, exportSizeLimit, }: { savedObjectsClient: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; exportSizeLimit: number; }); exportByObjects(options: SavedObjectsExportByObjectOptions): Promise; @@ -2418,8 +2420,10 @@ export class SavedObjectsExportError extends Error { readonly attributes?: Record | undefined; // (undocumented) static exportSizeExceeded(limit: number): SavedObjectsExportError; + static invalidTransformError(objectKeys: string[]): SavedObjectsExportError; // (undocumented) static objectFetchError(objects: SavedObject[]): SavedObjectsExportError; + static objectTransformError(objects: SavedObject[], cause: Error): SavedObjectsExportError; // (undocumented) readonly type: string; } @@ -2434,6 +2438,14 @@ export interface SavedObjectsExportResultDetails { }>; } +// @public +export type SavedObjectsExportTransform = (context: SavedObjectsExportTransformContext, objects: Array>) => SavedObject[] | Promise; + +// @public +export interface SavedObjectsExportTransformContext { + request: KibanaRequest; +} + // @public export type SavedObjectsFieldMapping = SavedObjectsCoreFieldMapping | SavedObjectsComplexFieldMapping; @@ -2852,6 +2864,7 @@ export interface SavedObjectsTypeManagementDefinition { getTitle?: (savedObject: SavedObject) => string; icon?: string; importableAndExportable?: boolean; + onExport?: SavedObjectsExportTransform; onImport?: SavedObjectsImportHook; } From 7c6ee4a2f17218deffcab5a23fbc5582d2c811e0 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 10:41:01 +0100 Subject: [PATCH 15/18] nits --- src/core/server/saved_objects/export/errors.ts | 2 +- src/core/server/saved_objects/export/types.ts | 3 +++ .../saved_objects/saved_objects_type_registry.test.ts | 4 +++- src/core/server/saved_objects/types.ts | 7 +++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/server/saved_objects/export/errors.ts b/src/core/server/saved_objects/export/errors.ts index 8e4d09e1eea77..5720f3b2daf3e 100644 --- a/src/core/server/saved_objects/export/errors.ts +++ b/src/core/server/saved_objects/export/errors.ts @@ -57,7 +57,7 @@ export class SavedObjectsExportError extends Error { */ static invalidTransformError(objectKeys: string[]) { return new SavedObjectsExportError( - 'object-transform-error', + 'invalid-transform-error', 'Invalid transform performed on objects to export', { objectKeys, diff --git a/src/core/server/saved_objects/export/types.ts b/src/core/server/saved_objects/export/types.ts index abf3fbf91b510..bf7b265e45d29 100644 --- a/src/core/server/saved_objects/export/types.ts +++ b/src/core/server/saved_objects/export/types.ts @@ -84,6 +84,9 @@ export interface SavedObjectsExportTransformContext { /** * Transformation function used to mutate the exported objects of the associated type. * + * A type's export transform function will be executed once per user-initiated export, + * for all objects of that type. + * * @example * Registering a transform function changing the object's attributes during the export * ```ts diff --git a/src/core/server/saved_objects/saved_objects_type_registry.test.ts b/src/core/server/saved_objects/saved_objects_type_registry.test.ts index 6ae811a5b1119..0186af6e7628d 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.test.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.test.ts @@ -47,7 +47,7 @@ describe('SavedObjectTypeRegistry', () => { }).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`); }); - it('throws when `management.onExport` is specified but `management.importableAndExportable` is not or false', () => { + it('throws when `management.onExport` is specified but `management.importableAndExportable` is undefined or false', () => { expect(() => { registry.registerType( createType({ @@ -85,6 +85,8 @@ describe('SavedObjectTypeRegistry', () => { ); }).not.toThrow(); }); + + // TODO: same test with 'onImport' }); describe('#getType', () => { diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 41b2dea9cd3dc..4f47579741a5a 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -325,9 +325,11 @@ export interface SavedObjectsTypeManagementDefinition { * An optional export transform function that can be used transform the objects of the registered type during * the export process. * - * It can be used to either mutates the exported objects, or add new objects to the export list. + * It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list. * * See {@link SavedObjectsExportTransform | the transform type documentation} for more info and examples. + * + * @remarks `importableAndExportable` must be `true` to specify this property. */ onExport?: SavedObjectsExportTransform; /** @@ -369,7 +371,8 @@ export interface SavedObjectsTypeManagementDefinition { * } * ``` * - * @remark messages returned in the warnings are user facing and must be translated. + * @remarks messages returned in the warnings are user facing and must be translated. + * @remarks `importableAndExportable` must be `true` to specify this property. */ onImport?: SavedObjectsImportHook; } From a2c380aaa6f4aa046732961bac273def41893325 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 10:52:36 +0100 Subject: [PATCH 16/18] update generated doc --- docs/development/core/server/kibana-plugin-core-server.md | 2 +- ...ibana-plugin-core-server.savedobjectsexporttransform.md | 2 ++ ...gin-core-server.savedobjectstypemanagementdefinition.md | 2 +- ...server.savedobjectstypemanagementdefinition.onexport.md | 7 ++++++- ...server.savedobjectstypemanagementdefinition.onimport.md | 5 ++++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 01fc48dfae74d..82f4a285409c9 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -300,7 +300,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | -| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type. | +| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type.A type's export transform function will be executed once per user-initiated export, for all objects of that type. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. | | [SavedObjectsImportWarning](./kibana-plugin-core-server.savedobjectsimportwarning.md) | Composite type of all the possible types of import warnings.See [SavedObjectsImportSimpleWarning](./kibana-plugin-core-server.savedobjectsimportsimplewarning.md) and [SavedObjectsImportActionRequiredWarning](./kibana-plugin-core-server.savedobjectsimportactionrequiredwarning.md) for more details. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md index 14410e490b497..50d4c5425e8fd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsexporttransform.md @@ -6,6 +6,8 @@ Transformation function used to mutate the exported objects of the associated type. +A type's export transform function will be executed once per user-initiated export, for all objects of that type. + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md index cded5626d5ff1..e9cc2b12108d6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.md @@ -22,6 +22,6 @@ export interface SavedObjectsTypeManagementDefinition | [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | (savedObject: SavedObject<any>) => string | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. | | [icon](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.icon.md) | string | The eui icon name to display in the management table. If not defined, the default icon will be used. | | [importableAndExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.importableandexportable.md) | boolean | Is the type importable or exportable. Defaults to false. | -| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutates the exported objects, or add new objects to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | +| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | SavedObjectsExportTransform | An optional export transform function that can be used transform the objects of the registered type during the export process.It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. | | [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | SavedObjectsImportHook | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md index 44e211fa77e75..6302b36a73c68 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md @@ -6,7 +6,7 @@ An optional export transform function that can be used transform the objects of the registered type during the export process. -It can be used to either mutates the exported objects, or add new objects to the export list. +It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list. See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples. @@ -15,3 +15,8 @@ See [the transform type documentation](./kibana-plugin-core-server.savedobjectse ```typescript onExport?: SavedObjectsExportTransform; ``` + +## Remarks + +`importableAndExportable` must be `true` to specify this property. + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md index 55733ca5d4443..f6634c01c66ba 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md @@ -14,6 +14,10 @@ Import hooks are executed during the savedObjects import process and allow to in onImport?: SavedObjectsImportHook; ``` +## Remarks + +`importableAndExportable` must be `true` to specify this property. + ## Example Registering a hook displaying a warning about a specific type of object @@ -48,5 +52,4 @@ export class Plugin() { } ``` - messages returned in the warnings are user facing and must be translated. From 1a334268afcc31bd8e0467334e16c55f7998dcc2 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 10:58:01 +0100 Subject: [PATCH 17/18] rename test plugins --- .../plugins/saved_object_export_transforms/kibana.json | 8 ++++++++ .../package.json | 4 ++-- .../server/index.ts | 4 ++-- .../server/plugin.ts | 2 +- .../tsconfig.json | 0 .../plugins/saved_object_hooks/kibana.json | 8 -------- .../kibana.json | 4 ++-- .../package.json | 4 ++-- .../server/index.ts | 4 ++-- .../server/plugin.ts | 2 +- .../tsconfig.json | 0 11 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 test/plugin_functional/plugins/saved_object_export_transforms/kibana.json rename test/plugin_functional/plugins/{saved_object_export_hooks => saved_object_export_transforms}/package.json (84%) rename test/plugin_functional/plugins/{saved_object_export_hooks => saved_object_export_transforms}/server/index.ts (72%) rename test/plugin_functional/plugins/{saved_object_export_hooks => saved_object_export_transforms}/server/plugin.ts (97%) rename test/plugin_functional/plugins/{saved_object_export_hooks => saved_object_export_transforms}/tsconfig.json (100%) delete mode 100644 test/plugin_functional/plugins/saved_object_hooks/kibana.json rename test/plugin_functional/plugins/{saved_object_export_hooks => saved_object_import_warnings}/kibana.json (50%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/package.json (68%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/server/index.ts (73%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/server/plugin.ts (96%) rename test/plugin_functional/plugins/{saved_object_hooks => saved_object_import_warnings}/tsconfig.json (100%) diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json b/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json new file mode 100644 index 0000000000000..40b4c12f58e69 --- /dev/null +++ b/test/plugin_functional/plugins/saved_object_export_transforms/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "savedObjectExportTransforms", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["saved_object_export_transforms"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/package.json b/test/plugin_functional/plugins/saved_object_export_transforms/package.json similarity index 84% rename from test/plugin_functional/plugins/saved_object_export_hooks/package.json rename to test/plugin_functional/plugins/saved_object_export_transforms/package.json index fed568977c903..0ced0a3b21288 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/package.json +++ b/test/plugin_functional/plugins/saved_object_export_transforms/package.json @@ -1,7 +1,7 @@ { - "name": "saved_object_export_hooks", + "name": "saved_object_export_transforms", "version": "1.0.0", - "main": "target/test/plugin_functional/plugins/saved_object_export_hooks", + "main": "target/test/plugin_functional/plugins/saved_object_export_transforms", "kibana": { "version": "kibana", "templateVersion": "1.0.0" diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts similarity index 72% rename from test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts rename to test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts index fdd06c996ffb4..f87a7d7d2e6a3 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/server/index.ts +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/index.ts @@ -6,6 +6,6 @@ * Public License, v 1. */ -import { SavedObjectExportHooksPlugin } from './plugin'; +import { SavedObjectExportTransformsPlugin } from './plugin'; -export const plugin = () => new SavedObjectExportHooksPlugin(); +export const plugin = () => new SavedObjectExportTransformsPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts similarity index 97% rename from test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts rename to test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts index 9e4315c5d378f..f53dc1a396057 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; -export class SavedObjectExportHooksPlugin implements Plugin { +export class SavedObjectExportTransformsPlugin implements Plugin { public setup({ savedObjects, getStartServices }: CoreSetup, deps: {}) { const savedObjectStartContractPromise = getStartServices().then( ([{ savedObjects: savedObjectsStart }]) => savedObjectsStart diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json b/test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json similarity index 100% rename from test/plugin_functional/plugins/saved_object_export_hooks/tsconfig.json rename to test/plugin_functional/plugins/saved_object_export_transforms/tsconfig.json diff --git a/test/plugin_functional/plugins/saved_object_hooks/kibana.json b/test/plugin_functional/plugins/saved_object_hooks/kibana.json deleted file mode 100644 index 1580e1862fac1..0000000000000 --- a/test/plugin_functional/plugins/saved_object_hooks/kibana.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "savedObjectHooks", - "version": "0.0.1", - "kibanaVersion": "kibana", - "configPath": ["saved_object_hooks"], - "server": true, - "ui": false -} diff --git a/test/plugin_functional/plugins/saved_object_export_hooks/kibana.json b/test/plugin_functional/plugins/saved_object_import_warnings/kibana.json similarity index 50% rename from test/plugin_functional/plugins/saved_object_export_hooks/kibana.json rename to test/plugin_functional/plugins/saved_object_import_warnings/kibana.json index a1af7495a122c..947f840560eba 100644 --- a/test/plugin_functional/plugins/saved_object_export_hooks/kibana.json +++ b/test/plugin_functional/plugins/saved_object_import_warnings/kibana.json @@ -1,8 +1,8 @@ { - "id": "savedObjectExportHooks", + "id": "savedObjectImportWarnings", "version": "0.0.1", "kibanaVersion": "kibana", - "configPath": ["saved_object_export_hooks"], + "configPath": ["saved_object_import_warnings"], "server": true, "ui": false } diff --git a/test/plugin_functional/plugins/saved_object_hooks/package.json b/test/plugin_functional/plugins/saved_object_import_warnings/package.json similarity index 68% rename from test/plugin_functional/plugins/saved_object_hooks/package.json rename to test/plugin_functional/plugins/saved_object_import_warnings/package.json index 9e09e5fc94be4..0c3cb50bd0b18 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/package.json +++ b/test/plugin_functional/plugins/saved_object_import_warnings/package.json @@ -1,7 +1,7 @@ { - "name": "saved_object_hooks", + "name": "saved_object_import_warnings", "version": "1.0.0", - "main": "target/test/plugin_functional/plugins/saved_object_hooks", + "main": "target/test/plugin_functional/plugins/saved_object_import_warnings", "kibana": { "version": "kibana", "templateVersion": "1.0.0" diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/index.ts b/test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts similarity index 73% rename from test/plugin_functional/plugins/saved_object_hooks/server/index.ts rename to test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts index 28aaa75961ddc..9a7209480cc19 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/server/index.ts +++ b/test/plugin_functional/plugins/saved_object_import_warnings/server/index.ts @@ -6,6 +6,6 @@ * Public License, v 1. */ -import { SavedObjectHooksPlugin } from './plugin'; +import { SavedObjectImportWarningsPlugin } from './plugin'; -export const plugin = () => new SavedObjectHooksPlugin(); +export const plugin = () => new SavedObjectImportWarningsPlugin(); diff --git a/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts b/test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts similarity index 96% rename from test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts rename to test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts index 823d9a90f29e2..5fc4e4aed9b90 100644 --- a/test/plugin_functional/plugins/saved_object_hooks/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_import_warnings/server/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreSetup } from 'kibana/server'; -export class SavedObjectHooksPlugin implements Plugin { +export class SavedObjectImportWarningsPlugin implements Plugin { public setup({ savedObjects }: CoreSetup, deps: {}) { savedObjects.registerType({ name: 'test_import_warning_1', diff --git a/test/plugin_functional/plugins/saved_object_hooks/tsconfig.json b/test/plugin_functional/plugins/saved_object_import_warnings/tsconfig.json similarity index 100% rename from test/plugin_functional/plugins/saved_object_hooks/tsconfig.json rename to test/plugin_functional/plugins/saved_object_import_warnings/tsconfig.json From 912da125e434419545ccf9dd94367f2c5b8fb448 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 21 Jan 2021 12:51:34 +0100 Subject: [PATCH 18/18] adding FTR tests on export failures --- .../export_transform/data.json | 34 ++++++++++++++ .../export_transform/mappings.json | 10 +++++ .../server/plugin.ts | 45 +++++++++++++++++++ .../export_transform.ts | 42 +++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json index f5c8bd4f51c59..7f4043958bc89 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/data.json @@ -113,3 +113,37 @@ } } } + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-invalid-transform:type_3-obj_1", + "source": { + "test-export-invalid-transform": { + "title": "test_2-obj_1" + }, + "type": "test-export-invalid-transform", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "type": "doc", + "id": "test-export-transform-error:type_4-obj_1", + "source": { + "test-export-transform-error": { + "title": "test_2-obj_1" + }, + "type": "test-export-transform-error", + "migrationVersion": {}, + "updated_at": "2018-12-21T00:43:07.096Z" + } + } +} diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json index 1bd2dd0ba24b3..d85125efd672a 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/export_transform/mappings.json @@ -28,6 +28,16 @@ "title": { "type": "text" } } }, + "test-export-transform-error": { + "properties": { + "title": { "type": "text" } + } + }, + "test-export-invalid-transform": { + "properties": { + "title": { "type": "text" } + } + }, "apm-telemetry": { "properties": { "has_any_services": { diff --git a/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts index f53dc1a396057..acbf454a93093 100644 --- a/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts +++ b/test/plugin_functional/plugins/saved_object_export_transforms/server/plugin.ts @@ -89,6 +89,51 @@ export class SavedObjectExportTransformsPlugin implements Plugin { getTitle: (obj) => obj.attributes.title, }, }); + + ///////////// + ///////////// + // example of a SO type that will throw an object-transform-error + savedObjects.registerType({ + name: 'test-export-transform-error', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + throw new Error('Error during transform'); + }, + }, + }); + + // example of a SO type that will throw an invalid-transform-error + savedObjects.registerType({ + name: 'test-export-invalid-transform', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + management: { + defaultSearchField: 'title', + importableAndExportable: true, + getTitle: (obj) => obj.attributes.title, + onExport: (ctx, objs) => { + return objs.map((obj) => ({ + ...obj, + id: `${obj.id}-mutated`, + })); + }, + }, + }); } public start() {} diff --git a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts index 7996bc48e5aa9..33c4ddc38be07 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/export_transform.ts @@ -94,5 +94,47 @@ export default function ({ getService }: PluginFunctionalProviderContext) { ]); }); }); + + it('returns a 400 when the type causes a transform error', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-transform-error'], + excludeExportDetails: true, + }) + .expect(400) + .then((resp) => { + const { attributes, ...error } = resp.body; + expect(error).to.eql({ + error: 'Bad Request', + message: 'Error transforming objects to export', + statusCode: 400, + }); + expect(attributes.cause).to.eql('Error during transform'); + expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); + }); + }); + + it('returns a 400 when the type causes an invalid transform', async () => { + await supertest + .post('/api/saved_objects/_export') + .set('kbn-xsrf', 'true') + .send({ + type: ['test-export-invalid-transform'], + excludeExportDetails: true, + }) + .expect(400) + .then((resp) => { + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'Invalid transform performed on objects to export', + statusCode: 400, + attributes: { + objectKeys: ['test-export-invalid-transform|type_3-obj_1'], + }, + }); + }); + }); }); }