mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Saved Objects] Adds managed to import options (#155677)
Co-authored-by: Alejandro Fernández Haro <afharo@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
483edea966
commit
0858b388f4
33 changed files with 1265 additions and 86 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -80,10 +80,12 @@ describe('#importSavedObjectsFromStream', () => {
|
|||
management: { icon: `${type}-icon` },
|
||||
} as any),
|
||||
importHooks = {},
|
||||
managed,
|
||||
}: {
|
||||
createNewCopies?: boolean;
|
||||
getTypeImpl?: (name: string) => any;
|
||||
importHooks?: Record<string, SavedObjectsImportHook[]>;
|
||||
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', () => {
|
||||
|
|
|
@ -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<SavedObjectsImportResponse> {
|
||||
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 }),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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([]),
|
||||
|
|
|
@ -19,31 +19,53 @@ import {
|
|||
|
||||
type CreateSavedObjectsParams = Parameters<typeof createSavedObjects>[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<LegacyUrlAlias> => ({
|
||||
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.
|
||||
|
|
|
@ -30,6 +30,14 @@ export interface CreateSavedObjectsParams<T> {
|
|||
* 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<T> {
|
||||
|
@ -50,6 +58,7 @@ export const createSavedObjects = async <T>({
|
|||
overwrite,
|
||||
refresh,
|
||||
compatibilityMode,
|
||||
managed,
|
||||
}: CreateSavedObjectsParams<T>): Promise<CreateSavedObjectsResult<T>> => {
|
||||
// filter out any objects that resulted in errors
|
||||
const errorSet = accumulatedErrors.reduce(
|
||||
|
@ -96,7 +105,12 @@ export const createSavedObjects = async <T>({
|
|||
...(!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 <T>({
|
|||
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 <T>({
|
|||
})
|
||||
).saved_objects
|
||||
: [];
|
||||
|
||||
return {
|
||||
createdObjects: remappedResults.filter((obj) => !obj.error),
|
||||
errors: extractErrors(remappedResults, objects, legacyUrlAliasResults, legacyUrlAliases),
|
||||
|
|
|
@ -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)",
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string, SavedObjectsImportHook[]>;
|
||||
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
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<SavedObjectsImportResponse> {
|
||||
// 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
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -57,6 +57,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter {
|
|||
overwrite,
|
||||
refresh,
|
||||
compatibilityMode,
|
||||
managed,
|
||||
}: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse> {
|
||||
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<SavedObjectsImportResponse> {
|
||||
return resolveSavedObjectsImportErrors({
|
||||
readStream,
|
||||
|
@ -89,6 +92,7 @@ export class SavedObjectsImporter implements ISavedObjectsImporter {
|
|||
savedObjectsClient: this.#savedObjectsClient,
|
||||
typeRegistry: this.#typeRegistry,
|
||||
importHooks: this.#importHooks,
|
||||
managed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<T> = SavedObject<T> & { destinationId?: string };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
},
|
||||
|
|
|
@ -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();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 () =>
|
||||
|
|
|
@ -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: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
|
|
4
test/api_integration/fixtures/import_managed.ndjson
Normal file
4
test/api_integration/fixtures/import_managed.ndjson
Normal file
|
@ -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}
|
|
@ -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
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -47,6 +47,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
meta: {
|
||||
title: 'my title',
|
||||
},
|
||||
managed: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -59,6 +59,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
title: 'new title!',
|
||||
},
|
||||
overwrite: true,
|
||||
managed: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue