diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts index afafd67fcab3a6..1ffc5fcde62d86 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts @@ -293,11 +293,5 @@ export function setManaged({ optionsManaged?: boolean; objectManaged?: boolean; }): boolean { - if (optionsManaged !== undefined) { - return optionsManaged; - } else if (optionsManaged === undefined && objectManaged !== undefined) { - return objectManaged; - } else { - return false; - } + return optionsManaged ?? objectManaged ?? false; } diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/legacy_alias/types.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/legacy_alias/types.ts index 9148b4e903c822..b0fc589e30c3dc 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/legacy_alias/types.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/legacy_alias/types.ts @@ -59,4 +59,10 @@ export interface LegacyUrlAlias { * created because of saved object conversion, then we will display a toast telling the user that the object has a new URL. */ purpose?: 'savedObjectConversion' | 'savedObjectImport'; + /** + * Flag indicating if a saved object is managed by Kibana (default=false). + * Only used when upserting a saved object. If the saved object already + * exist this option has no effect. + */ + managed?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-common/src/saved_objects_imports.ts b/packages/core/saved-objects/core-saved-objects-common/src/saved_objects_imports.ts index 7d6700ed3b47c6..85d221c0c58aff 100644 --- a/packages/core/saved-objects/core-saved-objects-common/src/saved_objects_imports.ts +++ b/packages/core/saved-objects/core-saved-objects-common/src/saved_objects_imports.ts @@ -91,6 +91,7 @@ export interface SavedObjectsImportFailure { * If `overwrite` is specified, an attempt was made to overwrite an existing object. */ overwrite?: boolean; + managed?: boolean; error: | SavedObjectsImportConflictError | SavedObjectsImportAmbiguousConflictError @@ -125,6 +126,14 @@ export interface SavedObjectsImportSuccess { * If `overwrite` is specified, this object overwrote an existing one (or will do so, in the case of a pending resolution). */ overwrite?: boolean; + /** + * Flag indicating if a saved object is managed by Kibana (default=false) + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } /** diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts index 66a010b548a4ae..8e6576de34e019 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts @@ -80,10 +80,12 @@ describe('#importSavedObjectsFromStream', () => { management: { icon: `${type}-icon` }, } as any), importHooks = {}, + managed, }: { createNewCopies?: boolean; getTypeImpl?: (name: string) => any; importHooks?: Record; + managed?: boolean; } = {}): ImportSavedObjectsOptions => { readStream = new Readable(); savedObjectsClient = savedObjectsClientMock.create(); @@ -98,19 +100,23 @@ describe('#importSavedObjectsFromStream', () => { namespace, createNewCopies, importHooks, + managed, }; }; const createObject = ({ type = 'foo-type', title = 'some-title', - }: { type?: string; title?: string } = {}): SavedObject<{ + managed = undefined, // explicitly declare undefined so as not set to test against existing objects + }: { type?: string; title?: string; managed?: boolean } = {}): SavedObject<{ title: string; + managed?: boolean; }> => { return { type, id: uuidv4(), references: [], attributes: { title }, + managed, }; }; const createError = (): SavedObjectsImportFailure => { @@ -320,6 +326,55 @@ describe('#importSavedObjectsFromStream', () => { importStateMap, overwrite, namespace, + managed: options.managed, + }; + expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); + }); + + test('creates managed saved objects', async () => { + const options = setupOptions({ managed: true }); + const collectedObjects = [createObject({ managed: true })]; + const filteredObjects = [createObject({ managed: false })]; + const errors = [createError(), createError(), createError(), createError()]; + mockCollectSavedObjects.mockResolvedValue({ + errors: [errors[0]], + collectedObjects, + importStateMap: new Map([ + ['foo', {}], + ['bar', {}], + ['baz', { isOnlyReference: true }], + ]), + }); + mockCheckReferenceOrigins.mockResolvedValue({ + importStateMap: new Map([['baz', { isOnlyReference: true, destinationId: 'newId1' }]]), + }); + mockValidateReferences.mockResolvedValue([errors[1]]); + mockCheckConflicts.mockResolvedValue({ + errors: [errors[2]], + filteredObjects, + importStateMap: new Map([['foo', { destinationId: 'newId2' }]]), + pendingOverwrites: new Set(), + }); + mockCheckOriginConflicts.mockResolvedValue({ + errors: [errors[3]], + importStateMap: new Map([['bar', { destinationId: 'newId3' }]]), + pendingOverwrites: new Set(), + }); + + await importSavedObjectsFromStream(options); + const importStateMap = new Map([ + ['foo', { destinationId: 'newId2' }], + ['bar', { destinationId: 'newId3' }], + ['baz', { isOnlyReference: true, destinationId: 'newId1' }], + ]); + const createSavedObjectsParams = { + objects: collectedObjects, + accumulatedErrors: errors, + savedObjectsClient, + importStateMap, + overwrite, + namespace, + managed: options.managed, }; expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); }); @@ -383,6 +438,118 @@ describe('#importSavedObjectsFromStream', () => { expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); }); }); + + describe('managed option', () => { + test('if not provided, calls create without an override', async () => { + const options = setupOptions({ createNewCopies: true }); // weithout `managed` set + const collectedObjects = [ + createObject({ type: 'foo', managed: true }), + createObject({ type: 'bar', title: 'bar-title', managed: false }), + ]; + const errors = [createError(), createError()]; + mockCollectSavedObjects.mockResolvedValue({ + errors: [errors[0]], + collectedObjects, + importStateMap: new Map([ + ['foo', {}], + ['bar', { isOnlyReference: true }], + ]), + }); + mockCheckReferenceOrigins.mockResolvedValue({ + importStateMap: new Map([['bar', { isOnlyReference: true, destinationId: 'newId' }]]), + }); + mockValidateReferences.mockResolvedValue([errors[1]]); + mockRegenerateIds.mockReturnValue(new Map([['foo', { destinationId: `randomId1` }]])); + + await importSavedObjectsFromStream(options); + const importStateMap: ImportStateMap = new Map([ + ['foo', { destinationId: `randomId1` }], + ['bar', { isOnlyReference: true, destinationId: 'newId' }], + ]); + const createSavedObjectsParams = { + objects: collectedObjects, + accumulatedErrors: errors, + savedObjectsClient, + importStateMap, + overwrite, + namespace, + managed: undefined, + }; + expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); + }); // assert that the call to create will not override the object props. + + test('creates managed saved objects, overriding existing `managed` value', async () => { + const options = setupOptions({ createNewCopies: true, managed: true }); + const collectedObjects = [createObject({ managed: false })]; + const errors = [createError(), createError()]; + mockCollectSavedObjects.mockResolvedValue({ + errors: [errors[0]], + collectedObjects, + importStateMap: new Map([ + ['foo', {}], + ['bar', { isOnlyReference: true }], + ]), + }); + mockCheckReferenceOrigins.mockResolvedValue({ + importStateMap: new Map([['bar', { isOnlyReference: true, destinationId: 'newId' }]]), + }); + mockValidateReferences.mockResolvedValue([errors[1]]); + mockRegenerateIds.mockReturnValue(new Map([['foo', { destinationId: `randomId1` }]])); + + await importSavedObjectsFromStream(options); + // assert that the importStateMap is correctly composed of the results from the three modules + const importStateMap: ImportStateMap = new Map([ + ['foo', { destinationId: `randomId1` }], + ['bar', { isOnlyReference: true, destinationId: 'newId' }], + ]); + const createSavedObjectsParams = { + objects: collectedObjects, + accumulatedErrors: errors, + savedObjectsClient, + importStateMap, + overwrite, + namespace, + managed: true, + }; + expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); + }); + + test('creates and converts objects from managed to unmanaged', async () => { + const options = setupOptions({ createNewCopies: true, managed: false }); + const collectedObjects = [createObject({ managed: true })]; + const errors = [createError(), createError()]; + mockCollectSavedObjects.mockResolvedValue({ + errors: [errors[0]], + collectedObjects, + importStateMap: new Map([ + ['foo', {}], + ['bar', { isOnlyReference: true }], + ]), + }); + mockCheckReferenceOrigins.mockResolvedValue({ + importStateMap: new Map([['bar', { isOnlyReference: true, destinationId: 'newId' }]]), + }); + mockValidateReferences.mockResolvedValue([errors[1]]); + mockRegenerateIds.mockReturnValue(new Map([['foo', { destinationId: `randomId1` }]])); + + await importSavedObjectsFromStream(options); + // assert that the importStateMap is correctly composed of the results from the three modules + const importStateMap: ImportStateMap = new Map([ + ['foo', { destinationId: `randomId1` }], + ['bar', { isOnlyReference: true, destinationId: 'newId' }], + ]); + const createSavedObjectsParams = { + objects: collectedObjects, + accumulatedErrors: errors, + savedObjectsClient, + importStateMap, + overwrite, + namespace, + managed: false, + }; + expect(mockCreateSavedObjects).toHaveBeenCalledWith(createSavedObjectsParams); + }); + }); }); describe('results', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts index 8650e082c4163a..9d150386cee4dd 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts @@ -54,6 +54,10 @@ export interface ImportSavedObjectsOptions { * different Kibana versions (e.g. generate legacy URL aliases for all imported objects that have to change IDs). */ compatibilityMode?: boolean; + /** + * If provided, Kibana will apply the given option to the `managed` property. + */ + managed?: boolean; } /** @@ -73,6 +77,7 @@ export async function importSavedObjectsFromStream({ namespace, refresh, compatibilityMode, + managed, }: ImportSavedObjectsOptions): Promise { let errorAccumulator: SavedObjectsImportFailure[] = []; const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((type) => type.name); @@ -82,6 +87,7 @@ export async function importSavedObjectsFromStream({ readStream, objectLimit, supportedTypes, + managed, }); errorAccumulator = [...errorAccumulator, ...collectSavedObjectsResult.errors]; // Map of all IDs for objects that we are attempting to import, and any references that are not included in the read stream; @@ -154,12 +160,13 @@ export async function importSavedObjectsFromStream({ namespace, refresh, compatibilityMode, + managed, }; const createSavedObjectsResult = await createSavedObjects(createSavedObjectsParams); errorAccumulator = [...errorAccumulator, ...createSavedObjectsResult.errors]; const successResults = createSavedObjectsResult.createdObjects.map((createdObject) => { - const { type, id, destinationId, originId } = createdObject; + const { type, id, destinationId, originId, managed: createdObjectManaged } = createdObject; const getTitle = typeRegistry.getType(type)?.management?.getTitle; const meta = { title: getTitle ? getTitle(createdObject) : createdObject.attributes.title, @@ -170,6 +177,7 @@ export async function importSavedObjectsFromStream({ type, id, meta, + managed: createdObjectManaged ?? managed, ...(attemptedOverwrite && { overwrite: true }), ...(destinationId && { destinationId }), ...(destinationId && !originId && !createNewCopies && { createNewCopy: true }), diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.test.ts index 73a1f9913a4deb..b44ae1c48aa10f 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.test.ts @@ -51,6 +51,13 @@ describe('collectSavedObjects()', () => { attributes: { title: 'my title 2' }, references: [{ type: 'c', id: '3', name: 'c3' }], }; + const obj3 = { + type: 'bz', + id: '33', + attributes: { title: 'my title z' }, + references: [{ type: 'b', id: '2', name: 'b2' }], + managed: true, + }; describe('module calls', () => { test('limit stream with empty input stream is called with null', async () => { @@ -63,14 +70,15 @@ describe('collectSavedObjects()', () => { }); test('limit stream with non-empty input stream is called with all objects', async () => { - const readStream = createReadStream(obj1, obj2); + const readStream = createReadStream(obj1, obj2, obj3); const supportedTypes = [obj2.type]; await collectSavedObjects({ readStream, supportedTypes, objectLimit }); expect(createLimitStream).toHaveBeenCalledWith(objectLimit); - expect(limitStreamPush).toHaveBeenCalledTimes(3); + expect(limitStreamPush).toHaveBeenCalledTimes(4); expect(limitStreamPush).toHaveBeenNthCalledWith(1, obj1); expect(limitStreamPush).toHaveBeenNthCalledWith(2, obj2); + expect(limitStreamPush).toHaveBeenNthCalledWith(3, obj3); expect(limitStreamPush).toHaveBeenLastCalledWith(null); }); @@ -82,13 +90,14 @@ describe('collectSavedObjects()', () => { }); test('get non-unique entries with non-empty input stream is called with all entries', async () => { - const readStream = createReadStream(obj1, obj2); + const readStream = createReadStream(obj1, obj2, obj3); const supportedTypes = [obj2.type]; await collectSavedObjects({ readStream, supportedTypes, objectLimit }); expect(getNonUniqueEntries).toHaveBeenCalledWith([ { type: obj1.type, id: obj1.id }, { type: obj2.type, id: obj2.id }, + { type: obj3.type, id: obj3.id }, ]); }); @@ -101,7 +110,7 @@ describe('collectSavedObjects()', () => { }); test('filter with non-empty input stream is called with all objects of supported types', async () => { - const readStream = createReadStream(obj1, obj2); + const readStream = createReadStream(obj1, obj2, obj3); const filter = jest.fn(); const supportedTypes = [obj2.type]; await collectSavedObjects({ readStream, supportedTypes, objectLimit, filter }); @@ -139,8 +148,8 @@ describe('collectSavedObjects()', () => { const result = await collectSavedObjects({ readStream, supportedTypes, objectLimit }); const collectedObjects = [ - { ...obj1, typeMigrationVersion: '' }, - { ...obj2, typeMigrationVersion: '' }, + { ...obj1, typeMigrationVersion: '', managed: false }, + { ...obj2, typeMigrationVersion: '', managed: false }, ]; const importStateMap = new Map([ [`a:1`, {}], // a:1 is included because it is present in the collected objects @@ -166,7 +175,7 @@ describe('collectSavedObjects()', () => { const supportedTypes = [obj1.type]; const result = await collectSavedObjects({ readStream, supportedTypes, objectLimit }); - const collectedObjects = [{ ...obj1, typeMigrationVersion: '' }]; + const collectedObjects = [{ ...obj1, typeMigrationVersion: '', managed: false }]; const importStateMap = new Map([ [`a:1`, {}], // a:1 is included because it is present in the collected objects [`b:2`, { isOnlyReference: true }], // b:2 was filtered out due to an unsupported type; b:2 is included because a:1 has a reference to b:2, but this is marked as `isOnlyReference` because b:2 is not present in the collected objects @@ -180,8 +189,8 @@ describe('collectSavedObjects()', () => { test('keeps the original migration versions', async () => { const collectedObjects = [ - { ...obj1, migrationVersion: { a: '1.0.0' } }, - { ...obj2, typeMigrationVersion: '2.0.0' }, + { ...obj1, migrationVersion: { a: '1.0.0' }, managed: false }, + { ...obj2, typeMigrationVersion: '2.0.0', managed: false }, ]; const readStream = createReadStream(...collectedObjects); @@ -218,9 +227,10 @@ describe('collectSavedObjects()', () => { supportedTypes, objectLimit, filter, + managed: false, }); - const collectedObjects = [{ ...obj2, typeMigrationVersion: '' }]; + const collectedObjects = [{ ...obj2, typeMigrationVersion: '', managed: false }]; const importStateMap = new Map([ // a:1 was filtered out due to an unsupported type; a:1 is not included because there are no other references to a:1 [`b:2`, {}], // b:2 is included because it is present in the collected objects diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts index 92d8476759548c..d5362e8cdc5813 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts @@ -25,6 +25,7 @@ interface CollectSavedObjectsOptions { objectLimit: number; filter?: (obj: SavedObject) => boolean; supportedTypes: string[]; + managed?: boolean; } export async function collectSavedObjects({ @@ -32,6 +33,7 @@ export async function collectSavedObjects({ objectLimit, filter, supportedTypes, + managed, }: CollectSavedObjectsOptions) { const errors: SavedObjectsImportFailure[] = []; const entries: Array<{ type: string; id: string }> = []; @@ -68,6 +70,9 @@ export async function collectSavedObjects({ return { ...obj, ...(!obj.migrationVersion && !obj.typeMigrationVersion ? { typeMigrationVersion: '' } : {}), + // override any managed flag on an object with that given as an option otherwise set the default to avoid having to do that with a core migration transform + // this is a bulk operation, applied to all objects being imported + ...{ managed: managed ?? obj.managed ?? false }, }; }), createConcatStream([]), diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts index 6938c251992f52..ac6831626f2f2e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts @@ -19,31 +19,53 @@ import { type CreateSavedObjectsParams = Parameters[0]; +interface CreateOptions { + type: string; + id: string; + originId?: string; + managed?: boolean; +} +/** Utility function to add default `managed` flag to objects that don't have one declared. */ +const addManagedDefault = (objs: SavedObject[]) => + objs.map((obj) => ({ ...obj, managed: obj.managed ?? false })); /** * Function to create a realistic-looking import object given a type, ID, and optional originId */ -const createObject = (type: string, id: string, originId?: string): SavedObject => ({ +const createObject = (createOptions: CreateOptions): SavedObject => { + const { type, id, originId, managed } = createOptions; + return { + type, + id, + attributes: {}, + references: [ + { name: 'name-1', type: 'other-type', id: 'other-id' }, // object that is not present + { name: 'name-2', type: MULTI_NS_TYPE, id: 'id-1' }, // object that is present, but does not have an importStateMap entry + { name: 'name-3', type: MULTI_NS_TYPE, id: 'id-3' }, // object that is present and has an importStateMap entry + ], + ...(originId && { originId }), + ...(managed && { managed }), + }; +}; + +const createOptionsFrom = (type: string, id: string, originId?: string, managed?: boolean) => ({ type, id, - attributes: {}, - references: [ - { name: 'name-1', type: 'other-type', id: 'other-id' }, // object that is not present - { name: 'name-2', type: MULTI_NS_TYPE, id: 'id-1' }, // object that is present, but does not have an importStateMap entry - { name: 'name-3', type: MULTI_NS_TYPE, id: 'id-3' }, // object that is present and has an importStateMap entry - ], - ...(originId && { originId }), + originId, + managed, }); const createLegacyUrlAliasObject = ( sourceId: string, targetId: string, targetType: string, - targetNamespace: string = 'default' + targetNamespace: string = 'default', + managed?: boolean ): SavedObject => ({ type: LEGACY_URL_ALIAS_TYPE, id: `${targetNamespace}:${targetType}:${sourceId}`, attributes: { sourceId, targetNamespace, targetType, targetId, purpose: 'savedObjectImport' }, references: [], + managed: managed ?? false, }); const MULTI_NS_TYPE = 'multi'; @@ -51,19 +73,19 @@ const OTHER_TYPE = 'other'; /** * Create a variety of different objects to exercise different import / result scenarios */ -const obj1 = createObject(MULTI_NS_TYPE, 'id-1', 'originId-a'); // -> success -const obj2 = createObject(MULTI_NS_TYPE, 'id-2', 'originId-b'); // -> conflict -const obj3 = createObject(MULTI_NS_TYPE, 'id-3', 'originId-c'); // -> conflict (with known importId and omitOriginId=true) -const obj4 = createObject(MULTI_NS_TYPE, 'id-4', 'originId-d'); // -> conflict (with known importId) -const obj5 = createObject(MULTI_NS_TYPE, 'id-5', 'originId-e'); // -> unresolvable conflict -const obj6 = createObject(MULTI_NS_TYPE, 'id-6'); // -> success -const obj7 = createObject(MULTI_NS_TYPE, 'id-7'); // -> conflict -const obj8 = createObject(MULTI_NS_TYPE, 'id-8'); // -> conflict (with known importId) -const obj9 = createObject(MULTI_NS_TYPE, 'id-9'); // -> unresolvable conflict -const obj10 = createObject(OTHER_TYPE, 'id-10', 'originId-f'); // -> success -const obj11 = createObject(OTHER_TYPE, 'id-11', 'originId-g'); // -> conflict -const obj12 = createObject(OTHER_TYPE, 'id-12'); // -> success -const obj13 = createObject(OTHER_TYPE, 'id-13'); // -> conflict +const obj1 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-1', 'originId-a', true)); // -> success +const obj2 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-2', 'originId-b')); // -> conflict +const obj3 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-3', 'originId-c')); // -> conflict (with known importId and omitOriginId=true) +const obj4 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-4', 'originId-d')); // -> conflict (with known importId) +const obj5 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-5', 'originId-e')); // -> unresolvable conflict +const obj6 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-6', undefined, true)); // -> success +const obj7 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-7')); // -> conflict +const obj8 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-8')); // -> conflict (with known importId) +const obj9 = createObject(createOptionsFrom(MULTI_NS_TYPE, 'id-9')); // -> unresolvable conflict +const obj10 = createObject(createOptionsFrom(OTHER_TYPE, 'id-10', 'originId-f')); // -> success +const obj11 = createObject(createOptionsFrom(OTHER_TYPE, 'id-11', 'originId-g')); // -> conflict +const obj12 = createObject(createOptionsFrom(OTHER_TYPE, 'id-12')); // -> success +const obj13 = createObject(createOptionsFrom(OTHER_TYPE, 'id-13')); // -> conflict // non-multi-namespace types shouldn't have origin IDs, but we include test cases to ensure it's handled gracefully // non-multi-namespace types by definition cannot result in an unresolvable conflict, so we don't include test cases for those const importId3 = 'id-foo'; @@ -71,7 +93,7 @@ const importId4 = 'id-bar'; const importId8 = 'id-baz'; const importStateMap = new Map([ [`${obj3.type}:${obj3.id}`, { destinationId: importId3, omitOriginId: true }], - [`${obj4.type}:${obj4.id}`, { destinationId: importId4 }], + [`${obj4.type}:${obj4.id}`, { destinationId: importId4, managed: true }], [`${obj8.type}:${obj8.id}`, { destinationId: importId8 }], ]); @@ -92,6 +114,7 @@ describe('#createSavedObjects', () => { namespace?: string; overwrite?: boolean; compatibilityMode?: boolean; + managed?: boolean; }): CreateSavedObjectsParams => { savedObjectsClient = savedObjectsClientMock.create(); bulkCreate = savedObjectsClient.bulkCreate; @@ -99,7 +122,7 @@ describe('#createSavedObjects', () => { }; const getExpectedBulkCreateArgsObjects = (objects: SavedObject[], retry?: boolean) => - objects.map(({ type, id, attributes, originId }) => ({ + objects.map(({ type, id, attributes, originId, managed }) => ({ type, id: retry ? `new-id-for-${id}` : id, // if this was a retry, we regenerated the id -- this is mocked below attributes, @@ -110,13 +133,19 @@ describe('#createSavedObjects', () => { ], // if the import object had an originId, and/or if we regenerated the id, expect an originId to be included in the create args ...((originId || retry) && { originId: originId || id }), + ...(managed && { managed }), })); const expectBulkCreateArgs = { objects: (n: number, objects: SavedObject[], retry?: boolean) => { const expectedObjects = getExpectedBulkCreateArgsObjects(objects, retry); const expectedOptions = expect.any(Object); - expect(bulkCreate).toHaveBeenNthCalledWith(n, expectedObjects, expectedOptions); + const expectedObjectsWithManagedDefault = addManagedDefault(expectedObjects); + expect(bulkCreate).toHaveBeenNthCalledWith( + n, + expectedObjectsWithManagedDefault, + expectedOptions + ); }, legacyUrlAliases: (n: number, expectedAliasObjects: SavedObject[]) => { const expectedOptions = expect.any(Object); @@ -131,14 +160,16 @@ describe('#createSavedObjects', () => { const getResultMock = { success: ( - { type, id, attributes, references, originId }: SavedObject, - { namespace }: CreateSavedObjectsParams + { type, id, attributes, references, originId, managed: objectManaged }: SavedObject, + { namespace, managed }: CreateSavedObjectsParams ): SavedObject => ({ type, id, attributes, references, ...(originId && { originId }), + ...((managed && { managed }) ?? + (objectManaged && { managed: objectManaged }) ?? { managed: false }), version: 'some-version', updated_at: 'some-date', namespaces: [namespace ?? 'default'], @@ -253,7 +284,7 @@ describe('#createSavedObjects', () => { } }); - test('calls bulkCreate when unresolvable errors or no errors are present', async () => { + test('calls bulkCreate when unresolvable errors or no errors are present with docs that have managed set', async () => { for (const error of unresolvableErrors) { const options = setupParams({ objects: objs, accumulatedErrors: [error] }); setupMockResults(options); @@ -269,6 +300,7 @@ describe('#createSavedObjects', () => { test('when in compatibility mode, calls bulkCreate for legacy URL aliases when unresolvable errors or no errors are present', async () => { for (const error of unresolvableErrors) { + // options are ok, they return objects as declared const options = setupParams({ objects: objs, accumulatedErrors: [error], @@ -288,8 +320,8 @@ describe('#createSavedObjects', () => { }); }); - it('filters out version from objects before create', async () => { - const options = setupParams({ objects: [{ ...obj1, version: 'foo' }] }); + it('filters out version from objects before create and accepts managed', async () => { + const options = setupParams({ objects: [{ ...obj1, version: 'foo' }] }); // here optionsManaged is undefined bulkCreate.mockResolvedValue({ saved_objects: [getResultMock.success(obj1, options)] }); await createSavedObjects(options); @@ -299,8 +331,15 @@ describe('#createSavedObjects', () => { const testBulkCreateObjects = async ({ namespace, compatibilityMode, - }: { namespace?: string; compatibilityMode?: boolean } = {}) => { - const options = setupParams({ objects: objs, namespace, compatibilityMode }); + managed, + }: { namespace?: string; compatibilityMode?: boolean; managed?: boolean } = {}) => { + const objsWithMissingManaged = addManagedDefault(objs); + const options = setupParams({ + objects: objsWithMissingManaged, + namespace, + compatibilityMode, + managed, + }); setupMockResults(options); await createSavedObjects(options); @@ -310,7 +349,8 @@ describe('#createSavedObjects', () => { const x4 = { ...obj4, id: importId4 }; // this import object already has an originId const x8 = { ...obj8, id: importId8, originId: obj8.id }; // this import object doesn't have an originId, so it is set before create const argObjs = [obj1, obj2, x3, x4, obj5, obj6, obj7, x8, obj9, obj10, obj11, obj12, obj13]; - expectBulkCreateArgs.objects(1, argObjs); + const argObjsWithMissingManaged = addManagedDefault(argObjs); + expectBulkCreateArgs.objects(1, argObjsWithMissingManaged); if (compatibilityMode) { // Rewrite namespace in the legacy URL alias. diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts index a8de0bfc927507..9f0f98484d594f 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts @@ -30,6 +30,14 @@ export interface CreateSavedObjectsParams { * different Kibana versions (e.g. generate legacy URL aliases for all imported objects that have to change IDs). */ compatibilityMode?: boolean; + /** + * If true, create the object as managed. + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } export interface CreateSavedObjectsResult { @@ -50,6 +58,7 @@ export const createSavedObjects = async ({ overwrite, refresh, compatibilityMode, + managed, }: CreateSavedObjectsParams): Promise> => { // filter out any objects that resulted in errors const errorSet = accumulatedErrors.reduce( @@ -96,7 +105,12 @@ export const createSavedObjects = async ({ ...(!importStateValue.omitOriginId && { originId: originId ?? object.id }), }; } - return { ...object, ...(references && { references }), ...(originId && { originId }) }; + return { + ...object, + ...(references && { references }), + ...(originId && { originId }), + ...{ managed: managed ?? object.managed ?? false }, + }; }); const resolvableErrors = ['conflict', 'ambiguous_conflict', 'missing_references']; @@ -150,6 +164,7 @@ export const createSavedObjects = async ({ targetId: result.id, purpose: 'savedObjectImport', }, + ...{ managed: managed ?? false }, // we can safey create each doc with the given managed flag, even if it's set as the default, bulkCreate would "override" this otherwise. }); } } @@ -165,7 +180,6 @@ export const createSavedObjects = async ({ }) ).saved_objects : []; - return { createdObjects: remappedResults.filter((obj) => !obj.error), errors: extractErrors(remappedResults, objects, legacyUrlAliasResults, legacyUrlAliases), diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts index 26499dcefaf88d..5340139fe1ca07 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts @@ -31,6 +31,7 @@ describe('extractErrors()', () => { type: 'dashboard', attributes: { title: 'My Dashboard 1' }, references: [], + managed: false, }, { id: '2', @@ -38,6 +39,7 @@ describe('extractErrors()', () => { attributes: { title: 'My Dashboard 2' }, references: [], error: SavedObjectsErrorHelpers.createConflictError('dashboard', '2').output.payload, + managed: false, }, { id: '3', @@ -45,6 +47,7 @@ describe('extractErrors()', () => { attributes: { title: 'My Dashboard 3' }, references: [], error: SavedObjectsErrorHelpers.createBadRequestError().output.payload, + managed: false, }, { id: '4', @@ -53,6 +56,7 @@ describe('extractErrors()', () => { references: [], error: SavedObjectsErrorHelpers.createConflictError('dashboard', '4').output.payload, destinationId: 'foo', + managed: false, }, ]; const result = extractErrors(savedObjects, savedObjects, [], new Map()); @@ -63,6 +67,7 @@ describe('extractErrors()', () => { "type": "conflict", }, "id": "2", + "managed": false, "meta": Object { "title": "My Dashboard 2", }, @@ -76,6 +81,7 @@ describe('extractErrors()', () => { "type": "unknown", }, "id": "3", + "managed": false, "meta": Object { "title": "My Dashboard 3", }, @@ -87,6 +93,7 @@ describe('extractErrors()', () => { "type": "conflict", }, "id": "4", + "managed": false, "meta": Object { "title": "My Dashboard 4", }, @@ -104,6 +111,7 @@ describe('extractErrors()', () => { attributes: { title: 'My Dashboard 1' }, references: [], destinationId: 'one', + managed: false, }, { id: '2', @@ -111,6 +119,7 @@ describe('extractErrors()', () => { attributes: { title: 'My Dashboard 2' }, references: [], error: SavedObjectsErrorHelpers.createConflictError('dashboard', '2').output.payload, + managed: false, }, { id: '3', @@ -118,6 +127,7 @@ describe('extractErrors()', () => { attributes: { title: 'My Dashboard 3' }, references: [], destinationId: 'three', + managed: false, }, ]; @@ -135,6 +145,7 @@ describe('extractErrors()', () => { purpose: 'savedObjectImport', }, references: [], + managed: false, }, ], [ @@ -150,6 +161,7 @@ describe('extractErrors()', () => { purpose: 'savedObjectImport', }, references: [], + managed: false, }, ], ]); @@ -176,6 +188,7 @@ describe('extractErrors()', () => { "type": "conflict", }, "id": "2", + "managed": false, "meta": Object { "title": "My Dashboard 2", }, @@ -189,6 +202,7 @@ describe('extractErrors()', () => { "type": "unknown", }, "id": "default:dashboard:3", + "managed": false, "meta": Object { "title": "Legacy URL alias (3 -> three)", }, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts index 1f9ee579f39d58..0dad94a37df930 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts @@ -38,6 +38,7 @@ export function extractErrors( type: 'conflict', ...(destinationId && { destinationId }), }, + managed: savedObject.managed, }); continue; } @@ -49,6 +50,7 @@ export function extractErrors( ...savedObject.error, type: 'unknown', }, + managed: savedObject.managed, }); } } @@ -70,6 +72,7 @@ export function extractErrors( ...legacyUrlAliasResult.error, type: 'unknown', }, + managed: legacyUrlAlias.managed, }); } } diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts index ded3cb788eb9af..5b2a145b54dd6f 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts @@ -92,12 +92,14 @@ describe('#importSavedObjectsFromStream', () => { management: { icon: `${type}-icon` }, } as any), importHooks = {}, + managed, }: { retries?: SavedObjectsImportRetry[]; createNewCopies?: boolean; compatibilityMode?: boolean; getTypeImpl?: (name: string) => any; importHooks?: Record; + managed?: boolean; } = {}): ResolveSavedObjectsImportErrorsOptions => { readStream = new Readable(); savedObjectsClient = savedObjectsClientMock.create(); @@ -115,6 +117,7 @@ describe('#importSavedObjectsFromStream', () => { namespace, createNewCopies, compatibilityMode, + managed, }; }; @@ -128,7 +131,8 @@ describe('#importSavedObjectsFromStream', () => { }; const createObject = ( references?: SavedObjectReference[], - { type = 'foo-type', title = 'some-title' }: { type?: string; title?: string } = {} + { type = 'foo-type', title = 'some-title' }: { type?: string; title?: string } = {}, + managed?: boolean ): SavedObject<{ title: string; }> => { @@ -137,6 +141,7 @@ describe('#importSavedObjectsFromStream', () => { id: uuidv4(), references: references || [], attributes: { title }, + managed: managed ?? false, // apply the default that real createSavedObjects applies }; }; const createError = (): SavedObjectsImportFailure => { @@ -590,6 +595,62 @@ describe('#importSavedObjectsFromStream', () => { }); }); }); + describe('with managed option', () => { + test('applies managed option to overwritten objects if specified', async () => { + const objectCreated = createObject(); + const objectsToOverwrite = [{ ...objectCreated, managed: true }]; + const objectsToNotOverwrite = [createObject()]; + mockSplitOverwrites.mockReturnValue({ objectsToOverwrite, objectsToNotOverwrite }); + mockCreateSavedObjects.mockResolvedValueOnce({ + errors: [createError()], // this error will NOT be passed to the second `mockCreateSavedObjects` call + createdObjects: [], + }); + + await resolveSavedObjectsImportErrors(setupOptions({ managed: true })); + const partialCreateSavedObjectsParams = { + accumulatedErrors: [], + savedObjectsClient, + importStateMap: new Map(), + namespace, + managed: true, + }; + expect(mockCreateSavedObjects).toHaveBeenNthCalledWith(1, { + ...partialCreateSavedObjectsParams, + objects: objectsToOverwrite, + overwrite: true, + }); + expect(mockCreateSavedObjects).toHaveBeenNthCalledWith(2, { + ...partialCreateSavedObjectsParams, + objects: objectsToNotOverwrite, + }); + }); + test('if not specified, sets a default for objects that do not have managed specified', async () => { + const objectsToNotOverwrite = [{ ...createObject(), managed: false }]; + const objectsToOverwrite = [createObject()]; + mockSplitOverwrites.mockReturnValue({ objectsToOverwrite, objectsToNotOverwrite }); + mockCreateSavedObjects.mockResolvedValueOnce({ + errors: [createError()], // this error will NOT be passed to the second `mockCreateSavedObjects` call + createdObjects: [], + }); + + await resolveSavedObjectsImportErrors(setupOptions()); + const partialCreateSavedObjectsParams = { + accumulatedErrors: [], + savedObjectsClient, + importStateMap: new Map(), + namespace, + }; + expect(mockCreateSavedObjects).toHaveBeenNthCalledWith(1, { + ...partialCreateSavedObjectsParams, + objects: objectsToOverwrite, + overwrite: true, + }); + expect(mockCreateSavedObjects).toHaveBeenNthCalledWith(2, { + ...partialCreateSavedObjectsParams, + objects: objectsToNotOverwrite, + }); + }); + }); }); describe('results', () => { @@ -664,12 +725,14 @@ describe('#importSavedObjectsFromStream', () => { id: obj1.id, meta: { title: obj1.attributes.title, icon: `${obj1.type}-icon` }, overwrite: true, + managed: false, }, { type: obj2.type, id: obj2.id, meta: { title: obj2.attributes.title, icon: `${obj2.type}-icon` }, destinationId: obj2.destinationId, + managed: false, }, { type: obj3.type, @@ -677,6 +740,7 @@ describe('#importSavedObjectsFromStream', () => { meta: { title: obj3.attributes.title, icon: `${obj3.type}-icon` }, destinationId: obj3.destinationId, createNewCopy: true, + managed: false, }, ]; const errors = [ @@ -727,12 +791,14 @@ describe('#importSavedObjectsFromStream', () => { id: obj1.id, overwrite: true, meta: { title: 'getTitle-foo', icon: `${obj1.type}-icon` }, + managed: false, }, { type: obj2.type, id: obj2.id, overwrite: true, meta: { title: 'bar-title', icon: `${obj2.type}-icon` }, + managed: false, }, ]; @@ -771,5 +837,120 @@ describe('#importSavedObjectsFromStream', () => { warnings: [], }); }); + + test('does not apply a default for `managed` when not specified', async () => { + const obj1 = createObject([], { type: 'foo' }, true); + const obj2 = createObject([], { type: 'bar', title: 'bar-title' }); + + const options = setupOptions({ + getTypeImpl: (type) => { + if (type === 'foo') { + return { + management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + }; + } + return { + management: { icon: `${type}-icon` }, + }; + }, + }); + mockCheckConflicts.mockResolvedValue({ + errors: [], + filteredObjects: [], + importStateMap: new Map(), + pendingOverwrites: new Set(), + }); + mockCreateSavedObjects + .mockResolvedValueOnce({ + errors: [], + createdObjects: [obj1, { ...obj2, managed: false }], + }) // default applied in createSavedObjects + .mockResolvedValueOnce({ errors: [], createdObjects: [] }); + + const result = await resolveSavedObjectsImportErrors(options); + // successResults only includes the imported object's type, id, and destinationId (if a new one was generated) + const successResults = [ + { + type: obj1.type, + id: obj1.id, + overwrite: true, + meta: { title: 'getTitle-foo', icon: `${obj1.type}-icon` }, + managed: true, + }, + { + type: obj2.type, + id: obj2.id, + overwrite: true, + meta: { title: 'bar-title', icon: `${obj2.type}-icon` }, + managed: false, + }, + ]; + + expect(result).toEqual({ + success: true, + successCount: 2, + successResults, + warnings: [], + }); + }); // assert that the documents being imported retain their prop or have the default applied + test('applies `managed` to objects', async () => { + const obj1 = createObject([], { type: 'foo' }, true); + const obj2 = createObject([], { type: 'bar', title: 'bar-title' }); + + const options = setupOptions({ + getTypeImpl: (type) => { + if (type === 'foo') { + return { + management: { getTitle: () => 'getTitle-foo', icon: `${type}-icon` }, + }; + } + return { + management: { icon: `${type}-icon` }, + }; + }, + managed: true, + }); + mockCheckConflicts.mockResolvedValue({ + errors: [], + filteredObjects: [], + importStateMap: new Map(), + pendingOverwrites: new Set(), + }); + mockCreateSavedObjects + .mockResolvedValueOnce({ + errors: [], + createdObjects: [ + { ...obj1, managed: true }, + { ...obj2, managed: true }, + ], + }) // default applied in createSavedObjects + .mockResolvedValueOnce({ errors: [], createdObjects: [] }); + + const result = await resolveSavedObjectsImportErrors(options); + // successResults only includes the imported object's type, id, and destinationId (if a new one was generated) + const successResults = [ + { + type: obj1.type, + id: obj1.id, + overwrite: true, + meta: { title: 'getTitle-foo', icon: `${obj1.type}-icon` }, + managed: true, + }, + { + type: obj2.type, + id: obj2.id, + overwrite: true, + meta: { title: 'bar-title', icon: `${obj2.type}-icon` }, + managed: true, + }, + ]; + + expect(result).toEqual({ + success: true, + successCount: 2, + successResults, + warnings: [], + }); + }); // assert that the documents being imported retain their prop or have the default applied }); }); diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts index 8abacbeb310a40..ade85f02988c58 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts @@ -60,6 +60,10 @@ export interface ResolveSavedObjectsImportErrorsOptions { * different Kibana versions (e.g. generate legacy URL aliases for all imported objects that have to change IDs). */ compatibilityMode?: boolean; + /** If true, will create objects as managed. + * This property allows plugin authors to implement read-only UI's + */ + managed?: boolean; } /** @@ -78,6 +82,7 @@ export async function resolveSavedObjectsImportErrors({ namespace, createNewCopies, compatibilityMode, + managed, }: ResolveSavedObjectsImportErrorsOptions): Promise { // throw a BadRequest error if we see invalid retries validateRetries(retries); @@ -93,6 +98,7 @@ export async function resolveSavedObjectsImportErrors({ objectLimit, filter, supportedTypes, + managed, }); // Map of all IDs for objects that we are attempting to import, and any references that are not included in the read stream; // each value is empty by default @@ -112,6 +118,7 @@ export async function resolveSavedObjectsImportErrors({ // Replace references for (const savedObject of collectSavedObjectsResult.collectedObjects) { + // collectedObjects already have managed flag set const refMap = retriesReferencesMap.get(`${savedObject.type}:${savedObject.id}`); if (!refMap) { continue; @@ -205,13 +212,14 @@ export async function resolveSavedObjectsImportErrors({ overwrite?: boolean ) => { const createSavedObjectsParams = { - objects, + objects, // these objects only have a title, no other properties accumulatedErrors, savedObjectsClient, importStateMap, namespace, overwrite, compatibilityMode, + managed, }; const { createdObjects, errors: bulkCreateErrors } = await createSavedObjects( createSavedObjectsParams @@ -235,6 +243,7 @@ export async function resolveSavedObjectsImportErrors({ ...(overwrite && { overwrite }), ...(destinationId && { destinationId }), ...(destinationId && !originId && !createNewCopies && { createNewCopy: true }), + ...{ managed: createdObject.managed ?? managed ?? false }, // double sure that this already exists but doing a check just in case }; }), ]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts index 0a2b39993cd591..2c87395c255cdc 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts @@ -57,6 +57,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter { overwrite, refresh, compatibilityMode, + managed, }: SavedObjectsImportOptions): Promise { return importSavedObjectsFromStream({ readStream, @@ -69,6 +70,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter { savedObjectsClient: this.#savedObjectsClient, typeRegistry: this.#typeRegistry, importHooks: this.#importHooks, + managed, }); } @@ -78,6 +80,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter { compatibilityMode, namespace, retries, + managed, }: SavedObjectsResolveImportErrorsOptions): Promise { return resolveSavedObjectsImportErrors({ readStream, @@ -89,6 +92,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter { savedObjectsClient: this.#savedObjectsClient, typeRegistry: this.#typeRegistry, importHooks: this.#importHooks, + managed, }); } } diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts index d6382efd673fa7..156f9f42f792bd 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts @@ -31,11 +31,12 @@ describe('importDashboards(req)', () => { type: 'visualization', attributes: { visState: '{}' }, references: [], + managed: true, }, ]; }); - test('should call bulkCreate with each asset, filtering out any version if present', async () => { + test('should call bulkCreate with each asset, filtering out any version and managed if present', async () => { await importDashboards(savedObjectClient, importedObjects, { overwrite: false, exclude: [] }); expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1); diff --git a/packages/core/saved-objects/core-saved-objects-server/src/import.ts b/packages/core/saved-objects/core-saved-objects-server/src/import.ts index a10fcd4a237a9f..90d33e9339c436 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/import.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/import.ts @@ -69,6 +69,14 @@ export interface SavedObjectsImportOptions { * different Kibana versions (e.g. generate legacy URL aliases for all imported objects that have to change IDs). */ compatibilityMode?: boolean; + /** + * If true, will import as a managed object, else will import as not managed. + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } /** @@ -89,6 +97,14 @@ export interface SavedObjectsResolveImportErrorsOptions { * different Kibana versions (e.g. generate legacy URL aliases for all imported objects that have to change IDs). */ compatibilityMode?: boolean; + /** + * If true, will import as a managed object, else will import as not managed. + * + * This can be leveraged by applications to e.g. prevent edits to a managed + * saved object. Instead, users can be guided to create a copy first and + * make their edits to the copy. + */ + managed?: boolean; } export type CreatedObject = SavedObject & { destinationId?: string }; diff --git a/src/core/server/integration_tests/saved_objects/routes/import.test.ts b/src/core/server/integration_tests/saved_objects/routes/import.test.ts index 24116ff52267c0..0a9ae5f1646357 100644 --- a/src/core/server/integration_tests/saved_objects/routes/import.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/import.test.ts @@ -46,12 +46,14 @@ describe(`POST ${URL}`, () => { id: 'my-pattern', attributes: { title: 'my-pattern-*' }, references: [], + managed: false, }; const mockDashboard = { type: 'dashboard', id: 'my-dashboard', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; beforeEach(async () => { @@ -145,6 +147,7 @@ describe(`POST ${URL}`, () => { type: 'index-pattern', id: 'my-pattern', meta: { title: 'my-pattern-*', icon: 'index-pattern-icon' }, + managed: false, }, ], warnings: [], @@ -156,11 +159,49 @@ describe(`POST ${URL}`, () => { ); }); - it('imports an index pattern and dashboard, ignoring empty lines in the file', async () => { + it('returns the default for managed as part of the successResults', async () => { + savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [mockIndexPattern] }); + + const result = await supertest(httpSetup.server.listener) + .post(URL) + .set('content-Type', 'multipart/form-data; boundary=EXAMPLE') + .send( + [ + '--EXAMPLE', + 'Content-Disposition: form-data; name="file"; filename="export.ndjson"', + 'Content-Type: application/ndjson', + '', + '{"type":"index-pattern","id":"my-pattern","attributes":{"title":"my-pattern-*"}}', + '--EXAMPLE--', + ].join('\r\n') + ) + .expect(200); + + expect(result.body).toEqual({ + success: true, + successCount: 1, + successResults: [ + { + type: 'index-pattern', + id: 'my-pattern', + meta: { title: 'my-pattern-*', icon: 'index-pattern-icon' }, + managed: false, + }, + ], + warnings: [], + }); + expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present + expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( + [expect.objectContaining({ typeMigrationVersion: '', managed: false })], + expect.any(Object) // options + ); + }); + + it('imports an index pattern, dashboard and visualization, ignoring empty lines in the file', async () => { // NOTE: changes to this scenario should be reflected in the docs savedObjectsClient.bulkCreate.mockResolvedValueOnce({ - saved_objects: [mockIndexPattern, mockDashboard], + saved_objects: [mockIndexPattern, { ...mockDashboard, managed: false }], }); const result = await supertest(httpSetup.server.listener) @@ -190,11 +231,13 @@ describe(`POST ${URL}`, () => { type: mockIndexPattern.type, id: mockIndexPattern.id, meta: { title: mockIndexPattern.attributes.title, icon: 'index-pattern-icon' }, + managed: false, }, { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], warnings: [], @@ -235,6 +278,7 @@ describe(`POST ${URL}`, () => { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], errors: [ @@ -287,11 +331,13 @@ describe(`POST ${URL}`, () => { id: mockIndexPattern.id, meta: { title: mockIndexPattern.attributes.title, icon: 'index-pattern-icon' }, overwrite: true, + managed: false, }, { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], warnings: [], @@ -345,6 +391,7 @@ describe(`POST ${URL}`, () => { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], warnings: [], @@ -414,6 +461,7 @@ describe(`POST ${URL}`, () => { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], warnings: [], @@ -478,6 +526,7 @@ describe(`POST ${URL}`, () => { type: mockDashboard.type, id: mockDashboard.id, meta: { title: mockDashboard.attributes.title, icon: 'dashboard-icon' }, + managed: false, }, ], warnings: [], @@ -505,12 +554,14 @@ describe(`POST ${URL}`, () => { id: 'new-id-1', attributes: { title: 'Look at my visualization' }, references: [], + managed: false, }; const obj2 = { type: 'dashboard', id: 'new-id-2', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [obj1, obj2] }); @@ -539,12 +590,14 @@ describe(`POST ${URL}`, () => { id: 'my-vis', meta: { title: obj1.attributes.title, icon: 'visualization-icon' }, destinationId: obj1.id, + managed: false, }, { type: obj2.type, id: 'my-dashboard', meta: { title: obj2.attributes.title, icon: 'dashboard-icon' }, destinationId: obj2.id, + managed: false, }, ], warnings: [], @@ -556,11 +609,13 @@ describe(`POST ${URL}`, () => { type: 'visualization', id: 'new-id-1', references: [{ name: 'ref_0', type: 'index-pattern', id: 'my-pattern' }], + managed: false, }), expect.objectContaining({ type: 'dashboard', id: 'new-id-2', references: [{ name: 'ref_0', type: 'visualization', id: 'new-id-1' }], + managed: false, }), ], expect.any(Object) // options @@ -769,6 +824,7 @@ describe(`POST ${URL}`, () => { originId: 'my-vis', attributes: { title: 'Look at my visualization' }, references: [], + managed: false, }; const obj2 = { type: 'dashboard', @@ -776,6 +832,7 @@ describe(`POST ${URL}`, () => { originId: 'my-dashboard', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [ @@ -785,6 +842,7 @@ describe(`POST ${URL}`, () => { id: obj2.id, attributes: {}, references: [], + managed: false, error: { error: 'some-error', message: 'Why not?', statusCode: 503 }, }, ], @@ -830,6 +888,7 @@ describe(`POST ${URL}`, () => { id: obj1.originId, meta: { title: obj1.attributes.title, icon: 'visualization-icon' }, destinationId: obj1.id, + managed: false, }, ], errors: [ @@ -843,6 +902,7 @@ describe(`POST ${URL}`, () => { statusCode: 503, type: 'unknown', }, + managed: false, }, ], warnings: [], @@ -855,12 +915,14 @@ describe(`POST ${URL}`, () => { id: 'new-id-1', originId: 'my-vis', references: [{ name: 'ref_0', type: 'index-pattern', id: 'my-pattern' }], + managed: false, }), expect.objectContaining({ type: 'dashboard', id: 'new-id-2', originId: 'my-dashboard', references: [{ name: 'ref_0', type: 'visualization', id: 'new-id-1' }], + managed: false, }), ], expect.any(Object) // options @@ -905,7 +967,9 @@ describe(`POST ${URL}`, () => { attributes: { title: 'Look at my visualization' }, references: [], }; - savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [obj1] }); + savedObjectsClient.bulkCreate.mockResolvedValueOnce({ + saved_objects: [{ ...obj1, managed: false }], + }); // Prepare mock results for the created legacy URL alias (for obj1 only). const legacyUrlAliasObj1 = { @@ -956,6 +1020,7 @@ describe(`POST ${URL}`, () => { id: obj1.originId, meta: { title: obj1.attributes.title, icon: 'visualization-icon' }, destinationId: obj1.id, + managed: false, }, ], errors: [ @@ -969,6 +1034,7 @@ describe(`POST ${URL}`, () => { type: 'unknown', }, meta: { title: 'Legacy URL alias (my-vis -> new-id-1)', icon: 'legacy-url-alias-icon' }, + managed: false, }, ], warnings: [], @@ -981,6 +1047,7 @@ describe(`POST ${URL}`, () => { id: 'new-id-1', originId: 'my-vis', references: [{ name: 'ref_0', type: 'index-pattern', id: 'my-pattern' }], + managed: false, }), ], expect.any(Object) // options diff --git a/src/core/server/integration_tests/saved_objects/routes/resolve_import_errors.test.ts b/src/core/server/integration_tests/saved_objects/routes/resolve_import_errors.test.ts index 2202735df957f5..0e5af8e86be085 100644 --- a/src/core/server/integration_tests/saved_objects/routes/resolve_import_errors.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/resolve_import_errors.test.ts @@ -44,18 +44,21 @@ describe(`POST ${URL}`, () => { id: 'my-dashboard', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; const mockVisualization = { type: 'visualization', id: 'my-vis', attributes: { title: 'Look at my visualization' }, references: [{ name: 'ref_0', type: 'index-pattern', id: 'existing' }], + managed: false, }; const mockIndexPattern = { type: 'index-pattern', id: 'existing', attributes: {}, references: [], + managed: false, }; beforeEach(async () => { @@ -155,12 +158,13 @@ describe(`POST ${URL}`, () => { type, id, attributes: { title }, + managed, } = mockDashboard; const meta = { title, icon: 'dashboard-icon' }; expect(result.body).toEqual({ success: true, successCount: 1, - successResults: [{ type, id, meta }], + successResults: [{ type, id, meta, managed }], warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present @@ -193,17 +197,17 @@ describe(`POST ${URL}`, () => { ) .expect(200); - const { type, id, attributes } = mockDashboard; + const { type, id, attributes, managed } = mockDashboard; const meta = { title: attributes.title, icon: 'dashboard-icon' }; expect(result.body).toEqual({ success: true, successCount: 1, - successResults: [{ type, id, meta }], + successResults: [{ type, id, meta, managed }], warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( - [{ type, id, attributes, typeMigrationVersion: '' }], + [{ type, id, attributes, typeMigrationVersion: '', managed }], expect.objectContaining({ overwrite: undefined }) ); }); @@ -232,17 +236,17 @@ describe(`POST ${URL}`, () => { ) .expect(200); - const { type, id, attributes } = mockDashboard; + const { type, id, attributes, managed } = mockDashboard; const meta = { title: attributes.title, icon: 'dashboard-icon' }; expect(result.body).toEqual({ success: true, successCount: 1, - successResults: [{ type, id, meta, overwrite: true }], + successResults: [{ type, id, meta, overwrite: true, managed }], warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( - [{ type, id, attributes, typeMigrationVersion: '' }], + [{ type, id, attributes, typeMigrationVersion: '', managed }], expect.objectContaining({ overwrite: true }) ); }); @@ -271,7 +275,7 @@ describe(`POST ${URL}`, () => { ) .expect(200); - const { type, id, attributes, references } = mockVisualization; + const { type, id, attributes, references, managed } = mockVisualization; expect(result.body).toEqual({ success: true, successCount: 1, @@ -280,13 +284,14 @@ describe(`POST ${URL}`, () => { type: 'visualization', id: 'my-vis', meta: { title: 'Look at my visualization', icon: 'visualization-icon' }, + managed, }, ], warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( - [{ type, id, attributes, references, typeMigrationVersion: '' }], + [{ type, id, attributes, references, typeMigrationVersion: '', managed }], expect.objectContaining({ overwrite: undefined }) ); expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); @@ -319,7 +324,7 @@ describe(`POST ${URL}`, () => { ) .expect(200); - const { type, id, attributes } = mockVisualization; + const { type, id, attributes, managed } = mockVisualization; const references = [{ name: 'ref_0', type: 'index-pattern', id: 'missing' }]; expect(result.body).toEqual({ success: true, @@ -329,13 +334,14 @@ describe(`POST ${URL}`, () => { type: 'visualization', id: 'my-vis', meta: { title: 'Look at my visualization', icon: 'visualization-icon' }, + managed, }, ], warnings: [], }); expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith( - [{ type, id, attributes, references, typeMigrationVersion: '' }], + [{ type, id, attributes, references, typeMigrationVersion: '', managed }], expect.objectContaining({ overwrite: undefined }) ); expect(savedObjectsClient.bulkGet).not.toHaveBeenCalled(); @@ -351,12 +357,14 @@ describe(`POST ${URL}`, () => { id: 'new-id-1', attributes: { title: 'Look at my visualization' }, references: [], + managed: false, }; const obj2 = { type: 'dashboard', id: 'new-id-2', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [obj1, obj2] }); @@ -389,12 +397,14 @@ describe(`POST ${URL}`, () => { id: 'my-vis', meta: { title: obj1.attributes.title, icon: 'visualization-icon' }, destinationId: obj1.id, + managed: obj1.managed, }, { type: obj2.type, id: 'my-dashboard', meta: { title: obj2.attributes.title, icon: 'dashboard-icon' }, destinationId: obj2.id, + managed: obj2.managed, }, ], warnings: [], @@ -406,11 +416,13 @@ describe(`POST ${URL}`, () => { type: 'visualization', id: 'new-id-1', references: [{ name: 'ref_0', type: 'index-pattern', id: 'existing' }], + managed: false, }), expect.objectContaining({ type: 'dashboard', id: 'new-id-2', references: [{ name: 'ref_0', type: 'visualization', id: 'new-id-1' }], + managed: false, }), ], expect.any(Object) // options @@ -429,6 +441,7 @@ describe(`POST ${URL}`, () => { id: 'my-vis', attributes: { title: 'Look at my visualization' }, references: [], + managed: false, }; const obj2 = { type: 'dashboard', @@ -436,6 +449,7 @@ describe(`POST ${URL}`, () => { originId: 'my-dashboard', attributes: { title: 'Look at my dashboard' }, references: [], + managed: false, }; savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [obj1, obj2] }); @@ -451,6 +465,7 @@ describe(`POST ${URL}`, () => { targetId: obj2.id, purpose: 'savedObjectImport', }, + managed: false, }; savedObjectsClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [legacyUrlAliasObj2], @@ -484,12 +499,14 @@ describe(`POST ${URL}`, () => { type: obj1.type, id: 'my-vis', meta: { title: obj1.attributes.title, icon: 'visualization-icon' }, + managed: obj1.managed, }, { type: obj2.type, id: 'my-dashboard', meta: { title: obj2.attributes.title, icon: 'dashboard-icon' }, destinationId: obj2.id, + managed: obj2.managed, }, ], warnings: [], @@ -502,12 +519,14 @@ describe(`POST ${URL}`, () => { type: 'visualization', id: 'my-vis', references: [{ name: 'ref_0', type: 'index-pattern', id: 'existing' }], + managed: false, }), expect.objectContaining({ type: 'dashboard', id: 'new-id-2', originId: 'my-dashboard', references: [{ name: 'ref_0', type: 'visualization', id: 'my-vis' }], + managed: false, }), ], expect.any(Object) // options diff --git a/test/api_integration/apis/saved_objects/bulk_create.ts b/test/api_integration/apis/saved_objects/bulk_create.ts index b365df10f7d741..5119e43368cc57 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.ts +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -75,7 +75,7 @@ export default function ({ getService }: FtrProviderContext) { }, coreMigrationVersion: '8.8.0', typeMigrationVersion: resp.body.saved_objects[1].typeMigrationVersion, - managed: resp.body.saved_objects[1].managed, + managed: false, references: [], namespaces: [SPACE_ID], }, diff --git a/test/api_integration/apis/saved_objects/bulk_get.ts b/test/api_integration/apis/saved_objects/bulk_get.ts index 6c97efd94d70a0..c3adf21b6c55ea 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.ts +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -28,17 +28,42 @@ export default function ({ getService }: FtrProviderContext) { }, ]; + const BULK_REQUESTS_MANAGED = [ + { + type: 'visualization', + id: '3fdaa535-5baf-46bc-8265-705eda43b181', + }, + { + type: 'tag', + id: '0ed60f29-2021-4fd2-ba4e-943c61e2738c', + }, + { + type: 'tag', + id: '00ad6a46-6ac3-4f6c-892c-2f72c54a5e7d', + }, + { + type: 'dashboard', + id: '11fb046d-0e50-48a0-a410-a744b82cbffd', + }, + ]; + describe('_bulk_get', () => { before(async () => { await kibanaServer.importExport.load( 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json' + ); }); after(async () => { await kibanaServer.importExport.unload( 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json' + ); }); it('should return 200 with individual responses', async () => @@ -115,5 +140,130 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body.saved_objects[0].migrationVersion).to.be.ok(); expect(resp.body.saved_objects[0].typeMigrationVersion).to.be.ok(); })); + + it('should return 200 with individual responses that include the managed property of each object', async () => + await supertest + .post(`/api/saved_objects/_bulk_get`) + .send(BULK_REQUESTS_MANAGED) + .expect(200) + .then((resp) => { + const mockDate = '2015-01-01T00:00:00.000Z'; + resp.body.saved_objects[0].updated_at = mockDate; + resp.body.saved_objects[1].updated_at = mockDate; + resp.body.saved_objects[2].updated_at = mockDate; + resp.body.saved_objects[3].updated_at = mockDate; + resp.body.saved_objects[0].created_at = mockDate; + resp.body.saved_objects[1].created_at = mockDate; + resp.body.saved_objects[2].created_at = mockDate; + resp.body.saved_objects[3].created_at = mockDate; + expect(resp.body.saved_objects.length).to.eql(4); + expect(resp.body).to.eql({ + saved_objects: [ + { + id: '3fdaa535-5baf-46bc-8265-705eda43b181', + type: 'visualization', + namespaces: ['default'], + migrationVersion: { + visualization: '8.5.0', + }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.5.0', + updated_at: '2015-01-01T00:00:00.000Z', + created_at: '2015-01-01T00:00:00.000Z', + version: resp.body.saved_objects[0].version, + attributes: { + description: '', + kibanaSavedObjectMeta: { + searchSourceJSON: + resp.body.saved_objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, + }, + title: 'Managed Count of requests', + uiStateJSON: '{"spy":{"mode":{"name":null,"fill":false}}}', + version: resp.body.saved_objects[0].attributes.version, + visState: resp.body.saved_objects[0].attributes.visState, + }, + references: [ + { + id: '91200a00-9efd-11e7-acb3-3dab96693fab', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + }, + ], + managed: true, + }, + { + id: '0ed60f29-2021-4fd2-ba4e-943c61e2738c', + type: 'tag', + updated_at: '2015-01-01T00:00:00.000Z', + created_at: '2015-01-01T00:00:00.000Z', + namespaces: ['default'], + migrationVersion: { + tag: '8.0.0', + }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.0.0', + version: resp.body.saved_objects[1].version, + attributes: { color: '#E7664C', description: 'read-only', name: 'managed' }, + references: [], + managed: true, + }, + { + id: '00ad6a46-6ac3-4f6c-892c-2f72c54a5e7d', + type: 'tag', + namespaces: ['default'], + migrationVersion: { + tag: '8.0.0', + }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.0.0', + updated_at: '2015-01-01T00:00:00.000Z', + created_at: '2015-01-01T00:00:00.000Z', + version: resp.body.saved_objects[2].version, + attributes: { color: '#173a58', description: 'Editable', name: 'unmanaged' }, + references: [], + managed: false, + }, + { + id: '11fb046d-0e50-48a0-a410-a744b82cbffd', + type: 'dashboard', + namespaces: ['default'], + migrationVersion: { + dashboard: '8.7.0', + }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + updated_at: '2015-01-01T00:00:00.000Z', + created_at: '2015-01-01T00:00:00.000Z', + version: resp.body.saved_objects[3].version, + attributes: { + description: '', + hits: 0, + kibanaSavedObjectMeta: + resp.body.saved_objects[3].attributes.kibanaSavedObjectMeta, + optionsJSON: '{"darkTheme":false}', + panelsJSON: resp.body.saved_objects[3].attributes.panelsJSON, + refreshInterval: resp.body.saved_objects[3].attributes.refreshInterval, + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + title: 'Managed Requests', + version: resp.body.saved_objects[3].attributes.version, + }, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: '1:panel_1', + type: 'visualization', + }, + ], + managed: true, + }, + ], + }); + expect(resp.body.saved_objects[0].managed).to.be.ok(); + expect(resp.body.saved_objects[1].managed).to.be.ok(); + expect(resp.body.saved_objects[2].managed).not.to.be.ok(); + expect(resp.body.saved_objects[3].managed).to.be.ok(); + })); }); } diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index ec7b82453ffcec..0b22ec93c2b2c6 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -26,7 +26,9 @@ export default function ({ getService }: FtrProviderContext) { ); }); - after(() => kibanaServer.spaces.delete(SPACE_ID)); + after(async () => { + await kibanaServer.spaces.delete(SPACE_ID); + }); describe('basic amount of saved objects', () => { it('should return objects in dependency order', async () => { @@ -578,5 +580,46 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); }); }); + + describe('should retain the managed property value of exported saved objects', () => { + before(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json', + { space: SPACE_ID } + ); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_objects.json', + { space: SPACE_ID } + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_objects.json', + { space: SPACE_ID } + ); + }); + it('should retain all existing saved object properties', async () => { + // we're specifically asserting that the `managed` property isn't overwritten during export. + await supertest + .post(`/s/${SPACE_ID}/api/saved_objects/_export`) + .send({ + type: ['config', 'index-pattern'], + }) + .expect(200) + .then((resp) => { + const objects = ndjsonToObject(resp.text); + expect(objects).to.have.length(3); + expect(objects[0]).to.have.property('id', '6cda943f-a70e-43d4-b0cb-feb1b624cb62'); + expect(objects[0]).to.have.property('type', 'index-pattern'); + expect(objects[0]).to.have.property('managed', true); + expect(objects[1]).to.have.property('id', 'c1818992-bb2c-4a9a-b276-83ada7cce03e'); + expect(objects[1]).to.have.property('type', 'config'); + expect(objects[1]).to.have.property('managed', false); + expect(objects[2]).to.have.property('exportedCount', 2); + expect(objects[2]).to.have.property('missingRefCount', 0); + expect(objects[2].missingReferences).to.have.length(0); + }); + }); + }); }); } diff --git a/test/api_integration/apis/saved_objects/get.ts b/test/api_integration/apis/saved_objects/get.ts index a7b1f58c531f4f..bf4ca7d240a89d 100644 --- a/test/api_integration/apis/saved_objects/get.ts +++ b/test/api_integration/apis/saved_objects/get.ts @@ -18,11 +18,17 @@ export default function ({ getService }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json' + ); }); after(async () => { await kibanaServer.importExport.unload( 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json' + ); }); it('should return 200', async () => @@ -60,8 +66,52 @@ export default function ({ getService }: FtrProviderContext) { }); expect(resp.body.migrationVersion).to.be.ok(); expect(resp.body.typeMigrationVersion).to.be.ok(); - expect(resp.body.managed).to.not.be.ok(); + expect(resp.body.managed).not.to.be.ok(); })); + it("should return an object's managed property", async () => { + await supertest + .get(`/api/saved_objects/dashboard/11fb046d-0e50-48a0-a410-a744b82cbffd`) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + id: '11fb046d-0e50-48a0-a410-a744b82cbffd', + type: 'dashboard', + namespaces: ['default'], + migrationVersion: { + dashboard: '8.7.0', + }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + updated_at: resp.body.updated_at, + created_at: resp.body.created_at, + version: resp.body.version, + attributes: { + description: '', + hits: 0, + kibanaSavedObjectMeta: resp.body.attributes.kibanaSavedObjectMeta, + optionsJSON: '{"darkTheme":false}', + panelsJSON: resp.body.attributes.panelsJSON, + refreshInterval: resp.body.attributes.refreshInterval, + timeFrom: resp.body.attributes.timeFrom, + timeRestore: true, + timeTo: resp.body.attributes.timeTo, + title: 'Managed Requests', + version: resp.body.attributes.version, + }, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: '1:panel_1', + type: 'visualization', + }, + ], + managed: true, + }); + expect(resp.body.migrationVersion).to.be.ok(); + expect(resp.body.typeMigrationVersion).to.be.ok(); + expect(resp.body.managed).to.be.ok(); + }); + }); describe('doc does not exist', () => { it('should return same generic error as when index does not exist', async () => diff --git a/test/api_integration/apis/saved_objects/import.ts b/test/api_integration/apis/saved_objects/import.ts index 0d9cfed365a002..507c692194f927 100644 --- a/test/api_integration/apis/saved_objects/import.ts +++ b/test/api_integration/apis/saved_objects/import.ts @@ -40,6 +40,42 @@ export default function ({ getService }: FtrProviderContext) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', meta: { title: 'Requests', icon: 'dashboardApp' }, }; + const managedVis = { + id: '3fdaa535-5baf-46bc-8265-705eda43b181', + type: 'visualization', + meta: { + icon: 'visualizeApp', + title: 'Managed Count of requests', + }, + managed: true, + }; + const managedTag = { + id: '0ed60f29-2021-4fd2-ba4e-943c61e2738c', + type: 'tag', + meta: { + icon: 'tag', + title: 'managed', + }, + managed: true, + }; + const unmanagedTag = { + id: '00ad6a46-6ac3-4f6c-892c-2f72c54a5e7d', + type: 'tag', + meta: { + icon: 'tag', + title: 'unmanaged', + }, + managed: false, + }; + const managedDB = { + id: '11fb046d-0e50-48a0-a410-a744b82cbffd', + type: 'dashboard', + meta: { + icon: 'dashboardApp', + title: 'Managed Requests', + }, + managed: true, + }; describe('with basic data existing', () => { before(async () => { @@ -96,9 +132,9 @@ export default function ({ getService }: FtrProviderContext) { success: true, successCount: 3, successResults: [ - { ...indexPattern, overwrite: true }, - { ...visualization, overwrite: true }, - { ...dashboard, overwrite: true }, + { ...indexPattern, overwrite: true, managed: false }, + { ...visualization, overwrite: true, managed: false }, + { ...dashboard, overwrite: true, managed: false }, ], warnings: [], }); @@ -155,6 +191,7 @@ export default function ({ getService }: FtrProviderContext) { title: 'dashboard-b', }, type: 'dashboard', + managed: false, }, { id: 'dashboard-a', @@ -163,6 +200,7 @@ export default function ({ getService }: FtrProviderContext) { title: 'dashboard-a', }, type: 'dashboard', + managed: false, }, ], warnings: [], @@ -239,6 +277,74 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + it('should retain existing saved object managed property', async () => { + const objectsToImport = [ + JSON.stringify({ + type: 'config', + id: '1234', + attributes: {}, + references: [], + managed: true, + }), + ]; + await supertest + .post('/api/saved_objects/_import') + .attach('file', Buffer.from(objectsToImport.join('\n'), 'utf8'), 'export.ndjson') + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + success: true, + successCount: 1, + successResults: [ + { + id: '1234', + meta: { + title: 'Advanced Settings [1234]', + }, + type: 'config', + managed: true, + }, + ], + warnings: [], + }); + }); + }); + + it('should not overwrite managed if set on objects beging imported', async () => { + await supertest + .post('/api/saved_objects/_import') + .attach('file', join(__dirname, '../../fixtures/import_managed.ndjson')) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + success: true, + successCount: 4, + successResults: [managedVis, unmanagedTag, managedTag, managedDB], + warnings: [], + }); + }); + }); + it('should return 200 when conflicts exist but overwrite is passed in, without changing managed property on the object', async () => { + await supertest + .post('/api/saved_objects/_import') + .query({ overwrite: true }) + .attach('file', join(__dirname, '../../fixtures/import_managed.ndjson')) + .expect(200) + .then((resp) => { + expect(resp.body).to.eql({ + success: true, + successCount: 4, + successResults: [ + { ...managedVis, overwrite: true }, + { ...unmanagedTag, overwrite: true }, + { ...managedTag, overwrite: true }, + { ...managedDB, overwrite: true }, + ], + warnings: [], + }); + }); + }); }); }); } diff --git a/test/api_integration/apis/saved_objects/resolve_import_errors.ts b/test/api_integration/apis/saved_objects/resolve_import_errors.ts index 7ca61a26a11c1e..ce9ca42a1dedca 100644 --- a/test/api_integration/apis/saved_objects/resolve_import_errors.ts +++ b/test/api_integration/apis/saved_objects/resolve_import_errors.ts @@ -84,9 +84,9 @@ export default function ({ getService }: FtrProviderContext) { success: true, successCount: 3, successResults: [ - { ...indexPattern, overwrite: true }, - { ...visualization, overwrite: true }, - { ...dashboard, overwrite: true }, + { ...indexPattern, overwrite: true, managed: false }, + { ...visualization, overwrite: true, managed: false }, + { ...dashboard, overwrite: true, managed: false }, ], warnings: [], }); @@ -112,7 +112,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ success: true, successCount: 1, - successResults: [{ ...visualization, overwrite: true }], + successResults: [{ ...visualization, overwrite: true, managed: false }], warnings: [], }); }); @@ -163,6 +163,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'visualization', id: '1', meta: { title: 'My favorite vis', icon: 'visualizeApp' }, + managed: false, }, ], warnings: [], diff --git a/test/api_integration/fixtures/import_managed.ndjson b/test/api_integration/fixtures/import_managed.ndjson new file mode 100644 index 00000000000000..82ebc57a8b688b --- /dev/null +++ b/test/api_integration/fixtures/import_managed.ndjson @@ -0,0 +1,4 @@ +{"id":"3fdaa535-5baf-46bc-8265-705eda43b181","type":"visualization","namespaces":["default"],"coreMigrationVersion":"8.8.0","typeMigrationVersion":"8.5.0","updated_at":"2023-04-24T19:57:13.859Z","created_at":"2023-04-24T19:57:13.859Z","version":"WzExNCwxXQ==","attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Managed Count of requests","uiStateJSON":"{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}","version":1,"visState":"{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100,\"filter\":true},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\",\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}"},"references":[{"id":"91200a00-9efd-11e7-acb3-3dab96693fab","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"managed":true} +{"id":"00ad6a46-6ac3-4f6c-892c-2f72c54a5e7d","type":"tag","namespaces":["default"],"coreMigrationVersion":"8.8.0","typeMigrationVersion":"8.0.0","updated_at":"2023-04-24T19:58:24.550Z","created_at":"2023-04-24T19:58:24.550Z","version":"WzEyMSwxXQ==","attributes":{"color":"#173a58","description":"Editable","name":"unmanaged"},"references":[],"managed":false} +{"id":"0ed60f29-2021-4fd2-ba4e-943c61e2738c","type":"tag","namespaces":["default"],"coreMigrationVersion":"8.8.0","typeMigrationVersion":"8.0.0","updated_at":"2023-04-24T19:58:24.550Z","created_at":"2023-04-24T19:58:24.550Z","version":"WzEyMiwxXQ==","attributes":{"color":"#E7664C","description":"read-only","name":"managed"},"references":[],"managed":true} +{"id":"11fb046d-0e50-48a0-a410-a744b82cbffd","type":"dashboard","namespaces":["default"],"coreMigrationVersion":"8.8.0","typeMigrationVersion":"8.7.0","updated_at":"2023-04-24T20:54:57.921Z","created_at":"2023-04-24T20:54:57.921Z","version":"WzMwOSwxXQ==","attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"},"optionsJSON":"{\"darkTheme\":false}","panelsJSON":"[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":12,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"}]","refreshInterval":{"display":"Off","pause":false,"value":0},"timeFrom":"Wed Sep 16 2015 22:52:17 GMT-0700","timeRestore":true,"timeTo":"Fri Sep 18 2015 12:24:38 GMT-0700","title":"Managed Requests","version":1},"references":[{"id":"dd7caf20-9efd-11e7-acb3-3dab96693fab","name":"1:panel_1","type":"visualization"}],"managed":true} diff --git a/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json b/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json new file mode 100644 index 00000000000000..4ca74083f824f2 --- /dev/null +++ b/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_basic.json @@ -0,0 +1,110 @@ +{ + "id": "3fdaa535-5baf-46bc-8265-705eda43b181", + "type": "visualization", + "namespaces": [ + "default" + ], + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.5.0", + "updated_at": "2023-04-24T19:57:13.859Z", + "created_at": "2023-04-24T19:57:13.859Z", + "version": "WzExNCwxXQ==", + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Managed Count of requests", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "version": 1, + "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100,\"filter\":true},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\",\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}" + }, + "references": [ + { + "id": "91200a00-9efd-11e7-acb3-3dab96693fab", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "managed": true +} + +{ + "id": "00ad6a46-6ac3-4f6c-892c-2f72c54a5e7d", + "type": "tag", + "namespaces": [ + "default" + ], + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-04-24T19:58:24.550Z", + "created_at": "2023-04-24T19:58:24.550Z", + "version": "WzEyMSwxXQ==", + "attributes": { + "color": "#173a58", + "description": "Editable", + "name": "unmanaged" + }, + "references": [], + "managed": false +} + +{ + "id": "0ed60f29-2021-4fd2-ba4e-943c61e2738c", + "type": "tag", + "namespaces": [ + "default" + ], + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-04-24T19:58:24.550Z", + "created_at": "2023-04-24T19:58:24.550Z", + "version": "WzEyMiwxXQ==", + "attributes": { + "color": "#E7664C", + "description": "read-only", + "name": "managed" + }, + "references": [], + "managed": true +} + +{ + "id": "11fb046d-0e50-48a0-a410-a744b82cbffd", + "type": "dashboard", + "namespaces": [ + "default" + ], + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.7.0", + "updated_at": "2023-04-24T20:54:57.921Z", + "created_at": "2023-04-24T20:54:57.921Z", + "version": "WzMwOSwxXQ==", + "attributes": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + }, + "optionsJSON": "{\"darkTheme\":false}", + "panelsJSON": "[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":12,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"}]", + "refreshInterval": { + "display": "Off", + "pause": false, + "value": 0 + }, + "timeFrom": "Wed Sep 16 2015 22:52:17 GMT-0700", + "timeRestore": true, + "timeTo": "Fri Sep 18 2015 12:24:38 GMT-0700", + "title": "Managed Requests", + "version": 1 + }, + "references": [ + { + "id": "dd7caf20-9efd-11e7-acb3-3dab96693fab", + "name": "1:panel_1", + "type": "visualization" + } + ], + "managed": true +} diff --git a/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_objects.json b/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_objects.json new file mode 100644 index 00000000000000..7aa1e872adf41d --- /dev/null +++ b/test/api_integration/fixtures/kbn_archiver/saved_objects/managed_objects.json @@ -0,0 +1,98 @@ +{ + "attributes": { + "buildNum": 8467, + "defaultIndex": "91200a00-9efd-11e7-acb3-3dab96693fab" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-04-21T23:19:26.101Z", + "id": "c1818992-bb2c-4a9a-b276-83ada7cce03e", + "managed": false, + "references": [], + "type": "config", + "typeMigrationVersion": "8.7.0", + "updated_at": "2023-04-21T23:19:26.101Z", + "version": "WzkzLDFd" +} + +{ + "attributes": { + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-04-21T23:19:26.101Z", + "id": "6cda943f-a70e-43d4-b0cb-feb1b624cb62", + "managed": true, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "7.11.0", + "updated_at": "2023-04-21T23:19:26.101Z", + "version": "Wzk0LDFd" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "title": "Count of requests", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "version": 1, + "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100,\"filter\":true},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\",\"legendSize\":\"auto\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-04-21T23:19:26.101Z", + "id": "37fb7899-4b68-4a5b-9b4c-f9587ab7f0bc", + "managed": true, + "references": [ + { + "id": "6cda943f-a70e-43d4-b0cb-feb1b624cb62", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "visualization", + "typeMigrationVersion": "8.5.0", + "updated_at": "2023-04-21T23:19:26.101Z", + "version": "Wzk1LDFd" +} + +{ + "attributes": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + }, + "optionsJSON": "{\"darkTheme\":false}", + "panelsJSON": "[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":12,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"}]", + "refreshInterval": { + "display": "Off", + "pause": false, + "value": 0 + }, + "timeFrom": "Wed Sep 16 2015 22:52:17 GMT-0700", + "timeRestore": true, + "timeTo": "Fri Sep 18 2015 12:24:38 GMT-0700", + "title": "Requests", + "version": 1 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-04-21T23:19:26.101Z", + "id": "def09d77-089a-4e4f-b30b-eb9848d59648", + "managed": true, + "references": [ + { + "id": "37fb7899-4b68-4a5b-9b4c-f9587ab7f0bc", + "name": "1:panel_1", + "type": "visualization" + } + ], + "type": "dashboard", + "typeMigrationVersion": "8.7.0", + "updated_at": "2023-04-21T23:19:26.101Z", + "version": "Wzk2LDFd" +} + diff --git a/test/plugin_functional/test_suites/saved_objects_hidden_type/import.ts b/test/plugin_functional/test_suites/saved_objects_hidden_type/import.ts index f8fa3c11a1b8ce..897170a5f6a303 100644 --- a/test/plugin_functional/test_suites/saved_objects_hidden_type/import.ts +++ b/test/plugin_functional/test_suites/saved_objects_hidden_type/import.ts @@ -47,6 +47,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { meta: { title: 'my title', }, + managed: false, }, ], }); diff --git a/test/plugin_functional/test_suites/saved_objects_hidden_type/resolve_import_errors.ts b/test/plugin_functional/test_suites/saved_objects_hidden_type/resolve_import_errors.ts index be2af8cd5d115f..26688ddd200464 100644 --- a/test/plugin_functional/test_suites/saved_objects_hidden_type/resolve_import_errors.ts +++ b/test/plugin_functional/test_suites/saved_objects_hidden_type/resolve_import_errors.ts @@ -59,6 +59,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { title: 'new title!', }, overwrite: true, + managed: false, }, ], }); diff --git a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts index d9516ee0331c45..c37bd671a764ff 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts @@ -188,6 +188,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { title: 'I am hidden from http apis but the client can still see me', }, type: 'test-hidden-from-http-apis-importable-exportable', + managed: false, }, { id: 'not-hidden-from-http-apis-import1', @@ -195,6 +196,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { title: 'I am not hidden from http apis', }, type: 'test-not-hidden-from-http-apis-importable-exportable', + managed: false, }, ], warnings: [], diff --git a/test/plugin_functional/test_suites/saved_objects_management/visible_in_management.ts b/test/plugin_functional/test_suites/saved_objects_management/visible_in_management.ts index c4cbd575dc0646..3567db0172ac81 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/visible_in_management.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/visible_in_management.ts @@ -89,6 +89,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { title: 'Saved object type that is not visible in management', }, type: 'test-not-visible-in-management', + managed: false, }, ], warnings: [], diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 4c5ae878bbf6e0..2325e0259c8dd1 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -182,6 +182,7 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { icon: 'dashboardApp', }, destinationId: dashboardDestinationId, + managed: false, }, ], }, @@ -229,24 +230,28 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { title: `Copy to Space index pattern 1 from ${spaceId} space`, }, destinationId: indexPatternDestinationId, + managed: false, }, { id: `cts_vis_1_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` }, destinationId: vis1DestinationId, + managed: false, }, { id: `cts_vis_2_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` }, destinationId: vis2DestinationId, + managed: false, }, { id: `cts_vis_3_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 3 from ${spaceId} space` }, destinationId: vis3DestinationId, + managed: false, }, { id: `cts_dashboard_${spaceId}`, @@ -256,6 +261,7 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { title: `This is the ${spaceId} test space CTS dashboard`, }, destinationId: dashboardDestinationId, + managed: false, }, ], }, @@ -357,18 +363,21 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { }, overwrite: true, destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId + managed: false, }, { id: `cts_vis_1_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` }, destinationId: vis1DestinationId, + managed: false, }, { id: `cts_vis_2_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` }, destinationId: vis2DestinationId, + managed: false, }, { id: `cts_vis_3_${spaceId}`, @@ -376,6 +385,7 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { meta: { icon: 'visualizeApp', title: `CTS vis 3 from ${spaceId} space` }, overwrite: true, destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId + managed: false, }, { id: `cts_dashboard_${spaceId}`, @@ -386,6 +396,7 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { }, overwrite: true, destinationId: `cts_dashboard_${destination}`, // this conflicted with another dashboard in the destination space because of a shared originId + managed: false, }, ], }, @@ -419,12 +430,14 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` }, destinationId: vis1DestinationId, + managed: false, }, { id: `cts_vis_2_${spaceId}`, type: 'visualization', meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` }, destinationId: vis2DestinationId, + managed: false, }, ]; const expectedErrors = [ @@ -522,7 +535,8 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { const destinationId = successResults![0].destinationId; expect(destinationId).to.match(UUID_PATTERN); const meta = { title, icon: 'beaker' }; - expect(successResults).to.eql([{ type, id: sourceId, meta, destinationId }]); + const managed = false; // default added By `create` + expect(successResults).to.eql([{ type, id: sourceId, meta, destinationId, managed }]); expect(errors).to.be(undefined); }; @@ -594,7 +608,14 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { expect(success).to.eql(true); expect(successCount).to.eql(1); expect(successResults).to.eql([ - { type, id: inexactMatchIdA, meta, overwrite: true, destinationId }, + { + type, + id: inexactMatchIdA, + meta, + overwrite: true, + destinationId, + managed: false, + }, ]); expect(errors).to.be(undefined); } else { @@ -635,7 +656,14 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { expect(success).to.eql(true); expect(successCount).to.eql(1); expect(successResults).to.eql([ - { type, id: inexactMatchIdB, meta, overwrite: true, destinationId }, + { + type, + id: inexactMatchIdB, + meta, + overwrite: true, + destinationId, + managed: false, + }, ]); expect(errors).to.be(undefined); } else { @@ -676,7 +704,14 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { expect(success).to.eql(true); expect(successCount).to.eql(1); expect(successResults).to.eql([ - { type, id: inexactMatchIdC, meta, overwrite: true, destinationId }, + { + type, + id: inexactMatchIdC, + meta, + overwrite: true, + destinationId, + managed: false, + }, ]); expect(errors).to.be(undefined); } else { diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 5f2c361714c49e..0a71beb9bffa0a 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -108,6 +108,7 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { }, destinationId: `cts_ip_1_${destination}`, // this conflicted with another index pattern in the destination space because of a shared originId overwrite: true, + managed: false, }, { id: `cts_vis_3_${sourceSpaceId}`, @@ -118,6 +119,7 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { }, destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId overwrite: true, + managed: false, }, ], }, @@ -147,6 +149,7 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { }, destinationId: `cts_dashboard_${destinationSpaceId}`, // this conflicted with another dashboard in the destination space because of a shared originId overwrite: true, + managed: false, }, ], }, @@ -380,7 +383,14 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { })(); const meta = { title, icon: 'beaker' }; expect(successResults).to.eql([ - { type, id, meta, overwrite: true, ...(destinationId && { destinationId }) }, + { + type, + id, + meta, + overwrite: true, + ...(destinationId && { destinationId }), + managed: false, + }, ]); };