mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Saved Objects] Update the migrationVersion
property to hold a plain string value (#150075)
* Update document migrator to rely on `typeMigrationVersion` instead of `migrationVersion`. * Refactor document migrator to extract migration pipeline logic. * Add `core` migration type.
This commit is contained in:
parent
1a0cab832d
commit
17876df41a
69 changed files with 1205 additions and 828 deletions
|
@ -252,7 +252,7 @@ Having said that, if a document is encountered that is not in the expected shape
|
|||
fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time. When such a scenario is encountered,
|
||||
the error should be verbose and informative so that the corrupt document can be corrected, if possible.
|
||||
|
||||
**WARNING:** Do not attempt to change the `migrationVersion`, `id`, or `type` fields within a migration function, this is not supported.
|
||||
**WARNING:** Do not attempt to change the `typeMigrationVersion`, `id`, or `type` fields within a migration function, this is not supported.
|
||||
|
||||
### Testing Migrations
|
||||
|
||||
|
|
|
@ -24,10 +24,15 @@ export interface SavedObjectsCreateOptions {
|
|||
id?: string;
|
||||
/** If a document with the given `id` already exists, overwrite it's contents (default=false). */
|
||||
overwrite?: boolean;
|
||||
/** {@inheritDoc SavedObjectsMigrationVersion} */
|
||||
/**
|
||||
* {@inheritDoc SavedObjectsMigrationVersion}
|
||||
* @deprecated
|
||||
*/
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
/** A semver value that is used when upgrading objects between Kibana versions. */
|
||||
coreMigrationVersion?: string;
|
||||
/** A semver value that is used when migrating documents between Kibana versions. */
|
||||
typeMigrationVersion?: string;
|
||||
/** Array of referenced saved objects. */
|
||||
references?: SavedObjectReference[];
|
||||
}
|
||||
|
|
|
@ -181,7 +181,6 @@ export interface SavedObjectsClientContract {
|
|||
* @param {object} attributes - the attributes to update
|
||||
* @param {object} options {@link SavedObjectsUpdateOptions}
|
||||
* @prop {integer} options.version - ensures version matches that of persisted object
|
||||
* @prop {object} options.migrationVersion - The optional migrationVersion of this document
|
||||
* @returns the udpated simple saved object
|
||||
* @deprecated See https://github.com/elastic/kibana/issues/149098
|
||||
*/
|
||||
|
|
|
@ -27,10 +27,15 @@ export interface SimpleSavedObject<T = unknown> {
|
|||
id: SavedObjectType<T>['id'];
|
||||
/** Type of the saved object */
|
||||
type: SavedObjectType<T>['type'];
|
||||
/** Migration version of the saved object */
|
||||
/**
|
||||
* Migration version of the saved object
|
||||
* @deprecated
|
||||
*/
|
||||
migrationVersion: SavedObjectType<T>['migrationVersion'];
|
||||
/** Core migration version of the saved object */
|
||||
coreMigrationVersion: SavedObjectType<T>['coreMigrationVersion'];
|
||||
/** Core migration version of the saved object */
|
||||
typeMigrationVersion: SavedObjectType<T>['typeMigrationVersion'];
|
||||
/** Error associated with this object, undefined if no error */
|
||||
error: SavedObjectType<T>['error'];
|
||||
/** References to other saved objects */
|
||||
|
|
|
@ -19,6 +19,7 @@ describe('getRootFields', () => {
|
|||
"references",
|
||||
"migrationVersion",
|
||||
"coreMigrationVersion",
|
||||
"typeMigrationVersion",
|
||||
"updated_at",
|
||||
"created_at",
|
||||
"originId",
|
||||
|
|
|
@ -17,6 +17,7 @@ const ROOT_FIELDS = [
|
|||
'references',
|
||||
'migrationVersion',
|
||||
'coreMigrationVersion',
|
||||
'typeMigrationVersion',
|
||||
'updated_at',
|
||||
'created_at',
|
||||
'originId',
|
||||
|
|
|
@ -94,6 +94,7 @@ describe('#getSavedObjectFromSource', () => {
|
|||
const references = [{ type: 'ref-type', id: 'ref-id', name: 'ref-name' }];
|
||||
const migrationVersion = { foo: 'migrationVersion' };
|
||||
const coreMigrationVersion = 'coreMigrationVersion';
|
||||
const typeMigrationVersion = 'typeMigrationVersion';
|
||||
const originId = 'originId';
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const updated_at = 'updatedAt';
|
||||
|
@ -112,6 +113,7 @@ describe('#getSavedObjectFromSource', () => {
|
|||
references,
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
originId,
|
||||
updated_at,
|
||||
...namespaceAttrs,
|
||||
|
@ -127,6 +129,7 @@ describe('#getSavedObjectFromSource', () => {
|
|||
expect(result).toEqual({
|
||||
attributes,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
id,
|
||||
migrationVersion,
|
||||
namespaces: expect.anything(), // see specific test cases below
|
||||
|
|
|
@ -149,6 +149,7 @@ export function getSavedObjectFromSource<T>(
|
|||
references: doc._source.references || [],
|
||||
migrationVersion: doc._source.migrationVersion,
|
||||
coreMigrationVersion: doc._source.coreMigrationVersion,
|
||||
typeMigrationVersion: doc._source.typeMigrationVersion,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -934,6 +934,8 @@ describe('SavedObjectsRepository', () => {
|
|||
_source: {
|
||||
...response.items[0].create._source,
|
||||
namespaces: response.items[0].create._source.namespaces,
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '1.1.1',
|
||||
},
|
||||
_id: expect.stringMatching(/^myspace:config:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/),
|
||||
});
|
||||
|
@ -942,6 +944,8 @@ describe('SavedObjectsRepository', () => {
|
|||
_source: {
|
||||
...response.items[1].create._source,
|
||||
namespaces: response.items[1].create._source.namespaces,
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '1.1.1',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -2946,7 +2950,8 @@ describe('SavedObjectsRepository', () => {
|
|||
attributes,
|
||||
references,
|
||||
namespaces: [namespace ?? 'default'],
|
||||
migrationVersion: { [MULTI_NAMESPACE_TYPE]: '1.1.1' },
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '1.1.1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3533,6 +3538,7 @@ describe('SavedObjectsRepository', () => {
|
|||
'references',
|
||||
'migrationVersion',
|
||||
'coreMigrationVersion',
|
||||
'typeMigrationVersion',
|
||||
'updated_at',
|
||||
'created_at',
|
||||
'originId',
|
||||
|
|
|
@ -303,6 +303,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
|
|||
const {
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
overwrite = false,
|
||||
references = [],
|
||||
refresh = DEFAULT_REFRESH_SETTING,
|
||||
|
@ -381,6 +382,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
|
|||
),
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
created_at: time,
|
||||
updated_at: time,
|
||||
...(Array.isArray(references) && { references }),
|
||||
|
@ -591,6 +593,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
|
|||
),
|
||||
migrationVersion: object.migrationVersion,
|
||||
coreMigrationVersion: object.coreMigrationVersion,
|
||||
typeMigrationVersion: object.typeMigrationVersion,
|
||||
...(savedObjectNamespace && { namespace: savedObjectNamespace }),
|
||||
...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }),
|
||||
updated_at: time,
|
||||
|
@ -2311,6 +2314,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
|
|||
): Promise<SavedObject<T>> {
|
||||
const {
|
||||
migrationVersion,
|
||||
typeMigrationVersion,
|
||||
refresh = DEFAULT_REFRESH_SETTING,
|
||||
initialize = false,
|
||||
upsertAttributes,
|
||||
|
@ -2384,6 +2388,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository {
|
|||
}, {} as Record<string, number>),
|
||||
},
|
||||
migrationVersion,
|
||||
typeMigrationVersion,
|
||||
updated_at: time,
|
||||
});
|
||||
|
||||
|
|
|
@ -589,23 +589,25 @@ export const getMockBulkCreateResponse = (
|
|||
return {
|
||||
errors: false,
|
||||
took: 1,
|
||||
items: objects.map(({ type, id, originId, attributes, references, migrationVersion }) => ({
|
||||
create: {
|
||||
// status: 1,
|
||||
// _index: '.kibana',
|
||||
_id: `${namespace ? `${namespace}:` : ''}${type}:${id}`,
|
||||
_source: {
|
||||
[type]: attributes,
|
||||
type,
|
||||
namespace,
|
||||
...(originId && { originId }),
|
||||
references,
|
||||
...mockTimestampFieldsWithCreated,
|
||||
migrationVersion: migrationVersion || { [type]: '1.1.1' },
|
||||
items: objects.map(
|
||||
({ type, id, originId, attributes, references, migrationVersion, typeMigrationVersion }) => ({
|
||||
create: {
|
||||
// status: 1,
|
||||
// _index: '.kibana',
|
||||
_id: `${namespace ? `${namespace}:` : ''}${type}:${id}`,
|
||||
_source: {
|
||||
[type]: attributes,
|
||||
type,
|
||||
namespace,
|
||||
...(originId && { originId }),
|
||||
references,
|
||||
...mockTimestampFieldsWithCreated,
|
||||
typeMigrationVersion: typeMigrationVersion || migrationVersion?.[type] || '1.1.1',
|
||||
},
|
||||
...mockVersionProps,
|
||||
},
|
||||
...mockVersionProps,
|
||||
},
|
||||
})),
|
||||
})
|
||||
),
|
||||
} as unknown as estypes.BulkResponse;
|
||||
};
|
||||
|
||||
|
@ -627,7 +629,8 @@ export const expectCreateResult = (obj: {
|
|||
namespaces?: string[];
|
||||
}) => ({
|
||||
...obj,
|
||||
migrationVersion: { [obj.type]: '1.1.1' },
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '1.1.1',
|
||||
version: mockVersion,
|
||||
namespaces: obj.namespaces ?? [obj.namespace ?? 'default'],
|
||||
...mockTimestampFieldsWithCreated,
|
||||
|
|
|
@ -25,7 +25,10 @@ export interface SavedObjectsBulkCreateObject<T = unknown> {
|
|||
version?: string;
|
||||
/** Array of references to other saved objects */
|
||||
references?: SavedObjectReference[];
|
||||
/** {@inheritDoc SavedObjectsMigrationVersion} */
|
||||
/**
|
||||
* {@inheritDoc SavedObjectsMigrationVersion}
|
||||
* @deprecated
|
||||
*/
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
/**
|
||||
* A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current
|
||||
|
@ -37,6 +40,8 @@ export interface SavedObjectsBulkCreateObject<T = unknown> {
|
|||
* field set and you want to create it again.
|
||||
*/
|
||||
coreMigrationVersion?: string;
|
||||
/** A semver value that is used when migrating documents between Kibana versions. */
|
||||
typeMigrationVersion?: string;
|
||||
/** Optional ID of the original saved object, if this object's `id` was regenerated */
|
||||
originId?: string;
|
||||
/**
|
||||
|
|
|
@ -25,7 +25,10 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
|
|||
* Can be used in conjunction with `overwrite` for implementing optimistic concurrency control.
|
||||
**/
|
||||
version?: string;
|
||||
/** {@inheritDoc SavedObjectsMigrationVersion} */
|
||||
/**
|
||||
* {@inheritDoc SavedObjectsMigrationVersion}
|
||||
* @deprecated Use {@link SavedObjectsCreateOptions.typeMigrationVersion} instead.
|
||||
*/
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
/**
|
||||
* A semver value that is used when upgrading objects between Kibana versions. If undefined, this will be automatically set to the current
|
||||
|
@ -37,6 +40,10 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
|
|||
* field set and you want to create it again.
|
||||
*/
|
||||
coreMigrationVersion?: string;
|
||||
/**
|
||||
* A semver value that is used when migrating documents between Kibana versions.
|
||||
*/
|
||||
typeMigrationVersion?: string;
|
||||
/** Array of references to other saved objects */
|
||||
references?: SavedObjectReference[];
|
||||
/** The Elasticsearch Refresh setting for this operation */
|
||||
|
|
|
@ -21,8 +21,15 @@ export interface SavedObjectsIncrementCounterOptions<Attributes = unknown>
|
|||
* already exist. Existing fields will be left as-is and won't be incremented.
|
||||
*/
|
||||
initialize?: boolean;
|
||||
/** {@link SavedObjectsMigrationVersion} */
|
||||
/**
|
||||
* {@link SavedObjectsMigrationVersion}
|
||||
* @deprecated
|
||||
*/
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
/**
|
||||
* A semver value that is used when migrating documents between Kibana versions.
|
||||
*/
|
||||
typeMigrationVersion?: string;
|
||||
/**
|
||||
* (default='wait_for') The Elasticsearch refresh setting for this
|
||||
* operation. See {@link MutatingOperationRefreshSetting}
|
||||
|
|
|
@ -76,7 +76,6 @@ export interface ISavedObjectsRepository {
|
|||
* @param {object} [options={}] {@link SavedObjectsCreateOptions} - options for the create operation
|
||||
* @property {string} [options.id] - force id on creation, not recommended
|
||||
* @property {boolean} [options.overwrite=false]
|
||||
* @property {object} [options.migrationVersion=undefined]
|
||||
* @property {string} [options.namespace]
|
||||
* @property {array} [options.references=[]] - [{ name, type, id }]
|
||||
* @returns {promise} the created saved object { id, type, version, attributes }
|
||||
|
|
|
@ -87,8 +87,15 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
|
|||
|
||||
const { namespaceTreatment = 'strict' } = options;
|
||||
const { _id, _source, _seq_no, _primary_term } = doc;
|
||||
const { type, namespaces, originId, migrationVersion, references, coreMigrationVersion } =
|
||||
_source;
|
||||
const {
|
||||
type,
|
||||
namespaces,
|
||||
originId,
|
||||
migrationVersion,
|
||||
references,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
} = _source;
|
||||
|
||||
const version =
|
||||
_seq_no != null || _primary_term != null
|
||||
|
@ -109,6 +116,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
|
|||
references: references || [],
|
||||
...(migrationVersion && { migrationVersion }),
|
||||
...(coreMigrationVersion && { coreMigrationVersion }),
|
||||
...(typeMigrationVersion != null ? { typeMigrationVersion } : {}),
|
||||
...(_source.updated_at && { updated_at: _source.updated_at }),
|
||||
...(_source.created_at && { created_at: _source.created_at }),
|
||||
...(version && { version }),
|
||||
|
@ -135,6 +143,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
|
|||
version,
|
||||
references,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
} = savedObj;
|
||||
const source = {
|
||||
[type]: attributes,
|
||||
|
@ -145,6 +154,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
|
|||
...(originId && { originId }),
|
||||
...(migrationVersion && { migrationVersion }),
|
||||
...(coreMigrationVersion && { coreMigrationVersion }),
|
||||
...(typeMigrationVersion != null ? { typeMigrationVersion } : {}),
|
||||
...(updated_at && { updated_at }),
|
||||
...(createdAt && { created_at: createdAt }),
|
||||
};
|
||||
|
|
|
@ -42,6 +42,7 @@ export const createSavedObjectSanitizedDocSchema = (attributesSchema: SavedObjec
|
|||
namespaces: schema.maybe(schema.arrayOf(schema.string())),
|
||||
migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())),
|
||||
coreMigrationVersion: schema.maybe(schema.string()),
|
||||
typeMigrationVersion: schema.maybe(schema.string()),
|
||||
updated_at: schema.maybe(schema.string()),
|
||||
created_at: schema.maybe(schema.string()),
|
||||
version: schema.maybe(schema.string()),
|
||||
|
|
|
@ -206,6 +206,7 @@ export class SavedObjectsClient implements SavedObjectsClientContract {
|
|||
body: JSON.stringify({
|
||||
attributes,
|
||||
migrationVersion: options.migrationVersion,
|
||||
typeMigrationVersion: options.typeMigrationVersion,
|
||||
references: options.references,
|
||||
}),
|
||||
});
|
||||
|
@ -216,7 +217,7 @@ export class SavedObjectsClient implements SavedObjectsClientContract {
|
|||
/**
|
||||
* Creates multiple documents at once
|
||||
*
|
||||
* @param {array} objects - [{ type, id, attributes, references, migrationVersion }]
|
||||
* @param {array} objects - [{ type, id, attributes, references, migrationVersion, typeMigrationVersion }]
|
||||
* @param {object} [options={}]
|
||||
* @property {boolean} [options.overwrite=false]
|
||||
* @returns The result of the create operation containing created saved objects.
|
||||
|
|
|
@ -25,8 +25,10 @@ export class SimpleSavedObjectImpl<T = unknown> implements SimpleSavedObject<T>
|
|||
public _version?: SavedObjectType<T>['version'];
|
||||
public id: SavedObjectType<T>['id'];
|
||||
public type: SavedObjectType<T>['type'];
|
||||
/** @deprecated */
|
||||
public migrationVersion: SavedObjectType<T>['migrationVersion'];
|
||||
public coreMigrationVersion: SavedObjectType<T>['coreMigrationVersion'];
|
||||
public typeMigrationVersion: SavedObjectType<T>['typeMigrationVersion'];
|
||||
public error: SavedObjectType<T>['error'];
|
||||
public references: SavedObjectType<T>['references'];
|
||||
public updatedAt: SavedObjectType<T>['updated_at'];
|
||||
|
@ -44,6 +46,7 @@ export class SimpleSavedObjectImpl<T = unknown> implements SimpleSavedObject<T>
|
|||
references,
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
namespaces,
|
||||
updated_at: updatedAt,
|
||||
created_at: createdAt,
|
||||
|
@ -56,6 +59,7 @@ export class SimpleSavedObjectImpl<T = unknown> implements SimpleSavedObject<T>
|
|||
this._version = version;
|
||||
this.migrationVersion = migrationVersion;
|
||||
this.coreMigrationVersion = coreMigrationVersion;
|
||||
this.typeMigrationVersion = typeMigrationVersion;
|
||||
this.namespaces = namespaces;
|
||||
this.updatedAt = updatedAt;
|
||||
this.createdAt = createdAt;
|
||||
|
@ -91,6 +95,7 @@ export class SimpleSavedObjectImpl<T = unknown> implements SimpleSavedObject<T>
|
|||
.create(this.type, this.attributes, {
|
||||
migrationVersion: this.migrationVersion,
|
||||
coreMigrationVersion: this.coreMigrationVersion,
|
||||
typeMigrationVersion: this.typeMigrationVersion,
|
||||
references: this.references,
|
||||
})
|
||||
.then((sso) => {
|
||||
|
|
|
@ -39,6 +39,7 @@ const createSimpleSavedObjectMock = (
|
|||
type: savedObject.type,
|
||||
migrationVersion: savedObject.migrationVersion,
|
||||
coreMigrationVersion: savedObject.coreMigrationVersion,
|
||||
typeMigrationVersion: savedObject.typeMigrationVersion,
|
||||
error: savedObject.error,
|
||||
references: savedObject.references,
|
||||
updatedAt: savedObject.updated_at,
|
||||
|
|
|
@ -80,10 +80,15 @@ export interface SavedObject<T = unknown> {
|
|||
attributes: T;
|
||||
/** {@inheritdoc SavedObjectReference} */
|
||||
references: SavedObjectReference[];
|
||||
/** {@inheritdoc SavedObjectsMigrationVersion} */
|
||||
/**
|
||||
* {@inheritdoc SavedObjectsMigrationVersion}
|
||||
* @deprecated Use `typeMigrationVersion` instead.
|
||||
*/
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
/** A semver value that is used when upgrading objects between Kibana versions. */
|
||||
coreMigrationVersion?: string;
|
||||
/** A semver value that is used when migrating documents between Kibana versions. */
|
||||
typeMigrationVersion?: string;
|
||||
/**
|
||||
* Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with
|
||||
* `namespaceType: 'agnostic'`.
|
||||
|
|
|
@ -139,8 +139,8 @@ describe('collectSavedObjects()', () => {
|
|||
const result = await collectSavedObjects({ readStream, supportedTypes, objectLimit });
|
||||
|
||||
const collectedObjects = [
|
||||
{ ...obj1, migrationVersion: {} },
|
||||
{ ...obj2, migrationVersion: {} },
|
||||
{ ...obj1, typeMigrationVersion: '' },
|
||||
{ ...obj2, typeMigrationVersion: '' },
|
||||
];
|
||||
const importStateMap = new Map([
|
||||
[`a:1`, {}], // a:1 is included because it is present in the collected objects
|
||||
|
@ -166,7 +166,7 @@ describe('collectSavedObjects()', () => {
|
|||
const supportedTypes = [obj1.type];
|
||||
const result = await collectSavedObjects({ readStream, supportedTypes, objectLimit });
|
||||
|
||||
const collectedObjects = [{ ...obj1, migrationVersion: {} }];
|
||||
const collectedObjects = [{ ...obj1, typeMigrationVersion: '' }];
|
||||
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
|
||||
|
@ -178,6 +178,19 @@ describe('collectSavedObjects()', () => {
|
|||
expect(result).toEqual({ collectedObjects, errors, importStateMap });
|
||||
});
|
||||
|
||||
test('keeps the original migration versions', async () => {
|
||||
const collectedObjects = [
|
||||
{ ...obj1, migrationVersion: { a: '1.0.0' } },
|
||||
{ ...obj2, typeMigrationVersion: '2.0.0' },
|
||||
];
|
||||
|
||||
const readStream = createReadStream(...collectedObjects);
|
||||
const supportedTypes = [obj1.type, obj2.type];
|
||||
const result = await collectSavedObjects({ readStream, supportedTypes, objectLimit });
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({ collectedObjects }));
|
||||
});
|
||||
|
||||
describe('with optional filter', () => {
|
||||
test('filters out objects when result === false', async () => {
|
||||
const readStream = createReadStream(obj1, obj2);
|
||||
|
@ -207,7 +220,7 @@ describe('collectSavedObjects()', () => {
|
|||
filter,
|
||||
});
|
||||
|
||||
const collectedObjects = [{ ...obj2, migrationVersion: {} }];
|
||||
const collectedObjects = [{ ...obj2, typeMigrationVersion: '' }];
|
||||
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
|
||||
|
|
|
@ -65,7 +65,10 @@ export async function collectSavedObjects({
|
|||
}
|
||||
}
|
||||
// Ensure migrations execute on every saved object
|
||||
return Object.assign({ migrationVersion: {} }, obj);
|
||||
return {
|
||||
...obj,
|
||||
...(!obj.migrationVersion && !obj.typeMigrationVersion ? { typeMigrationVersion: '' } : {}),
|
||||
};
|
||||
}),
|
||||
createConcatStream([]),
|
||||
]);
|
||||
|
|
|
@ -14,6 +14,7 @@ Object {
|
|||
"originId": "2f4316de49999235636386fe51dc06c1",
|
||||
"references": "7997cf5a56cc02bdc9c93361bde732b0",
|
||||
"type": "2f4316de49999235636386fe51dc06c1",
|
||||
"typeMigrationVersion": "539e3ecebb3abc1133618094cc3b7ae7",
|
||||
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
|
||||
},
|
||||
},
|
||||
|
@ -69,6 +70,9 @@ Object {
|
|||
"type": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"typeMigrationVersion": Object {
|
||||
"type": "version",
|
||||
},
|
||||
"updated_at": Object {
|
||||
"type": "date",
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ Object {
|
|||
"originId": "2f4316de49999235636386fe51dc06c1",
|
||||
"references": "7997cf5a56cc02bdc9c93361bde732b0",
|
||||
"type": "2f4316de49999235636386fe51dc06c1",
|
||||
"typeMigrationVersion": "539e3ecebb3abc1133618094cc3b7ae7",
|
||||
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
|
||||
},
|
||||
},
|
||||
|
@ -61,6 +62,9 @@ Object {
|
|||
"type": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"typeMigrationVersion": Object {
|
||||
"type": "version",
|
||||
},
|
||||
"updated_at": Object {
|
||||
"type": "date",
|
||||
},
|
||||
|
@ -83,6 +87,7 @@ Object {
|
|||
"secondType": "72d57924f415fbadb3ee293b67d233ab",
|
||||
"thirdType": "510f1f0adb69830cf8a1c5ce2923ed82",
|
||||
"type": "2f4316de49999235636386fe51dc06c1",
|
||||
"typeMigrationVersion": "539e3ecebb3abc1133618094cc3b7ae7",
|
||||
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
|
||||
},
|
||||
},
|
||||
|
@ -147,6 +152,9 @@ Object {
|
|||
"type": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"typeMigrationVersion": Object {
|
||||
"type": "version",
|
||||
},
|
||||
"updated_at": Object {
|
||||
"type": "date",
|
||||
},
|
||||
|
|
|
@ -159,6 +159,9 @@ export function getBaseMappings(): IndexMapping {
|
|||
coreMigrationVersion: {
|
||||
type: 'keyword',
|
||||
},
|
||||
typeMigrationVersion: {
|
||||
type: 'version',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,18 +31,36 @@ describe('migrateRawDocs', () => {
|
|||
transform,
|
||||
[
|
||||
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'a:c', _source: { type: 'a', a: { name: 'AAA' }, typeMigrationVersion: '1.0.0' } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
{
|
||||
_id: 'c:e',
|
||||
_source: { type: 'c', c: { name: 'DDD' }, migrationVersion: { c: '2.0.0' } },
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
_id: 'a:b',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'a:c',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '1.0.0', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'c:d',
|
||||
_source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'c', c: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'c:e',
|
||||
_source: {
|
||||
type: 'c',
|
||||
c: { name: 'HOI!' },
|
||||
migrationVersion: { c: '2.0.0' },
|
||||
references: [],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -50,19 +68,35 @@ describe('migrateRawDocs', () => {
|
|||
id: 'b',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
const obj2 = {
|
||||
id: 'c',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
typeMigrationVersion: '1.0.0',
|
||||
references: [],
|
||||
};
|
||||
const obj3 = {
|
||||
id: 'd',
|
||||
type: 'c',
|
||||
attributes: { name: 'DDD' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(2);
|
||||
const obj4 = {
|
||||
id: 'e',
|
||||
type: 'c',
|
||||
attributes: { name: 'DDD' },
|
||||
migrationVersion: { c: '2.0.0' },
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(4);
|
||||
expect(transform).toHaveBeenNthCalledWith(1, obj1);
|
||||
expect(transform).toHaveBeenNthCalledWith(2, obj2);
|
||||
expect(transform).toHaveBeenNthCalledWith(3, obj3);
|
||||
expect(transform).toHaveBeenNthCalledWith(4, obj4);
|
||||
});
|
||||
|
||||
test('throws when encountering a corrupt saved object document', async () => {
|
||||
|
@ -99,7 +133,7 @@ describe('migrateRawDocs', () => {
|
|||
expect(result).toEqual([
|
||||
{
|
||||
_id: 'a:b',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'foo:bar',
|
||||
|
@ -111,7 +145,7 @@ describe('migrateRawDocs', () => {
|
|||
id: 'b',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(1);
|
||||
|
@ -144,7 +178,12 @@ describe('migrateRawDocsSafely', () => {
|
|||
migrateDoc: transform,
|
||||
rawDocs: [
|
||||
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } },
|
||||
{ _id: 'a:c', _source: { type: 'a', a: { name: 'AAA' }, typeMigrationVersion: '1.0.0' } },
|
||||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } },
|
||||
{
|
||||
_id: 'c:e',
|
||||
_source: { type: 'c', c: { name: 'DDD' }, migrationVersion: { c: '2.0.0' } },
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = (await task()) as Either.Right<DocumentsTransformSuccess>;
|
||||
|
@ -152,11 +191,24 @@ describe('migrateRawDocsSafely', () => {
|
|||
expect(result.right.processedDocs).toEqual([
|
||||
{
|
||||
_id: 'a:b',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'a:c',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '1.0.0', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'c:d',
|
||||
_source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'c', c: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'c:e',
|
||||
_source: {
|
||||
type: 'c',
|
||||
c: { name: 'HOI!' },
|
||||
migrationVersion: { c: '2.0.0' },
|
||||
references: [],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -164,19 +216,35 @@ describe('migrateRawDocsSafely', () => {
|
|||
id: 'b',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
const obj2 = {
|
||||
id: 'c',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
typeMigrationVersion: '1.0.0',
|
||||
references: [],
|
||||
};
|
||||
const obj3 = {
|
||||
id: 'd',
|
||||
type: 'c',
|
||||
attributes: { name: 'DDD' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(2);
|
||||
const obj4 = {
|
||||
id: 'e',
|
||||
type: 'c',
|
||||
attributes: { name: 'DDD' },
|
||||
migrationVersion: { c: '2.0.0' },
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(4);
|
||||
expect(transform).toHaveBeenNthCalledWith(1, obj1);
|
||||
expect(transform).toHaveBeenNthCalledWith(2, obj2);
|
||||
expect(transform).toHaveBeenNthCalledWith(3, obj3);
|
||||
expect(transform).toHaveBeenNthCalledWith(4, obj4);
|
||||
});
|
||||
|
||||
test('returns a `left` tag when encountering a corrupt saved object document', async () => {
|
||||
|
@ -220,7 +288,7 @@ describe('migrateRawDocsSafely', () => {
|
|||
expect(result.right.processedDocs).toEqual([
|
||||
{
|
||||
_id: 'a:b',
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] },
|
||||
_source: { type: 'a', a: { name: 'HOI!' }, typeMigrationVersion: '', references: [] },
|
||||
},
|
||||
{
|
||||
_id: 'foo:bar',
|
||||
|
@ -232,7 +300,7 @@ describe('migrateRawDocsSafely', () => {
|
|||
id: 'b',
|
||||
type: 'a',
|
||||
attributes: { name: 'AAA' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [],
|
||||
};
|
||||
expect(transform).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -204,6 +204,8 @@ function convertToRawAddMigrationVersion(
|
|||
serializer: SavedObjectsSerializer
|
||||
): SavedObjectSanitizedDoc<unknown> {
|
||||
const savedObject = serializer.rawToSavedObject(rawDoc, options);
|
||||
savedObject.migrationVersion = savedObject.migrationVersion || {};
|
||||
if (!savedObject.migrationVersion && !savedObject.typeMigrationVersion) {
|
||||
savedObject.typeMigrationVersion = '';
|
||||
}
|
||||
return savedObject;
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const getCoreTransformsMock = jest.fn();
|
||||
export const getReferenceTransformsMock = jest.fn();
|
||||
export const getConversionTransformsMock = jest.fn();
|
||||
|
||||
jest.doMock('./internal_transforms', () => ({
|
||||
getCoreTransforms: getCoreTransformsMock,
|
||||
getReferenceTransforms: getReferenceTransformsMock,
|
||||
getConversionTransforms: getConversionTransformsMock,
|
||||
}));
|
||||
|
@ -27,6 +29,7 @@ jest.doMock('./validate_migrations', () => ({
|
|||
}));
|
||||
|
||||
export const resetAllMocks = () => {
|
||||
getCoreTransformsMock.mockReset().mockReturnValue([]);
|
||||
getReferenceTransformsMock.mockReset().mockReturnValue([]);
|
||||
getConversionTransformsMock.mockReset().mockReturnValue([]);
|
||||
getModelVersionTransformsMock.mockReset().mockReturnValue([]);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
getCoreTransformsMock,
|
||||
getConversionTransformsMock,
|
||||
getModelVersionTransformsMock,
|
||||
getReferenceTransformsMock,
|
||||
|
@ -221,6 +222,21 @@ describe('buildActiveMigrations', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('adds the transform from getCoreTransforms to each type', () => {
|
||||
const foo = createType({ name: 'foo' });
|
||||
const bar = createType({ name: 'bar' });
|
||||
|
||||
addType(foo);
|
||||
addType(bar);
|
||||
|
||||
getCoreTransformsMock.mockReturnValue([transform(TransformType.Core, '8.8.0')]);
|
||||
|
||||
const migrations = buildMigrations();
|
||||
expect(Object.keys(migrations).sort()).toEqual(['bar', 'foo']);
|
||||
expect(migrations.foo.transforms).toEqual([expectTransform(TransformType.Core, '8.8.0')]);
|
||||
expect(migrations.bar.transforms).toEqual([expectTransform(TransformType.Core, '8.8.0')]);
|
||||
});
|
||||
|
||||
it('calls getConversionTransforms with the correct parameters', () => {
|
||||
const foo = createType({ name: 'foo' });
|
||||
const bar = createType({ name: 'bar' });
|
||||
|
@ -285,6 +301,8 @@ describe('buildActiveMigrations', () => {
|
|||
}
|
||||
);
|
||||
|
||||
getCoreTransformsMock.mockReturnValue([transform(TransformType.Core, '8.8.0')]);
|
||||
|
||||
getReferenceTransformsMock.mockReturnValue([
|
||||
transform(TransformType.Reference, '7.12.0'),
|
||||
transform(TransformType.Reference, '7.17.3'),
|
||||
|
@ -302,6 +320,7 @@ describe('buildActiveMigrations', () => {
|
|||
|
||||
expect(Object.keys(migrations).sort()).toEqual(['bar', 'foo']);
|
||||
expect(migrations.foo.transforms).toEqual([
|
||||
expectTransform(TransformType.Core, '8.8.0'),
|
||||
expectTransform(TransformType.Reference, '7.12.0'),
|
||||
expectTransform(TransformType.Migrate, '7.12.0'),
|
||||
expectTransform(TransformType.Convert, '7.14.0'),
|
||||
|
@ -310,6 +329,7 @@ describe('buildActiveMigrations', () => {
|
|||
expectTransform(TransformType.Migrate, '7.18.2'),
|
||||
]);
|
||||
expect(migrations.bar.transforms).toEqual([
|
||||
expectTransform(TransformType.Core, '8.8.0'),
|
||||
expectTransform(TransformType.Reference, '7.12.0'),
|
||||
expectTransform(TransformType.Migrate, '7.17.0'),
|
||||
expectTransform(TransformType.Reference, '7.17.3'),
|
||||
|
|
|
@ -10,7 +10,11 @@ import _ from 'lodash';
|
|||
import type { Logger } from '@kbn/logging';
|
||||
import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
import { type ActiveMigrations, type Transform, type TypeTransforms, TransformType } from './types';
|
||||
import { getReferenceTransforms, getConversionTransforms } from './internal_transforms';
|
||||
import {
|
||||
getCoreTransforms,
|
||||
getReferenceTransforms,
|
||||
getConversionTransforms,
|
||||
} from './internal_transforms';
|
||||
import { validateTypeMigrations } from './validate_migrations';
|
||||
import { transformComparator, convertMigrationFunction } from './utils';
|
||||
import { getModelVersionTransforms } from './model_version';
|
||||
|
@ -32,6 +36,7 @@ export function buildActiveMigrations({
|
|||
convertVersion?: string;
|
||||
log: Logger;
|
||||
}): ActiveMigrations {
|
||||
const coreTransforms = getCoreTransforms();
|
||||
const referenceTransforms = getReferenceTransforms(typeRegistry);
|
||||
|
||||
return typeRegistry.getAllTypes().reduce((migrations, type) => {
|
||||
|
@ -41,6 +46,7 @@ export function buildActiveMigrations({
|
|||
type,
|
||||
log,
|
||||
kibanaVersion,
|
||||
coreTransforms,
|
||||
referenceTransforms,
|
||||
});
|
||||
|
||||
|
@ -58,11 +64,13 @@ export function buildActiveMigrations({
|
|||
const buildTypeTransforms = ({
|
||||
type,
|
||||
log,
|
||||
coreTransforms,
|
||||
referenceTransforms,
|
||||
}: {
|
||||
type: SavedObjectsType;
|
||||
kibanaVersion: string;
|
||||
log: Logger;
|
||||
coreTransforms: Transform[];
|
||||
referenceTransforms: Transform[];
|
||||
}): TypeTransforms => {
|
||||
const migrationsMap =
|
||||
|
@ -80,6 +88,7 @@ const buildTypeTransforms = ({
|
|||
|
||||
const conversionTransforms = getConversionTransforms(type);
|
||||
const transforms = [
|
||||
...coreTransforms,
|
||||
...referenceTransforms,
|
||||
...conversionTransforms,
|
||||
...migrationTransforms,
|
||||
|
|
|
@ -124,7 +124,7 @@ describe('DocumentMigrator', () => {
|
|||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Christopher' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
})
|
||||
).toThrow(/Migrations are not ready. Make sure prepareMigrations is called first./i);
|
||||
|
||||
|
@ -133,7 +133,7 @@ describe('DocumentMigrator', () => {
|
|||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Christopher' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
})
|
||||
).toThrow(/Migrations are not ready. Make sure prepareMigrations is called first./i);
|
||||
});
|
||||
|
@ -155,13 +155,15 @@ describe('DocumentMigrator', () => {
|
|||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Christopher' },
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Chris' },
|
||||
migrationVersion: { user: '1.2.3' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.2.3',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -183,40 +185,14 @@ describe('DocumentMigrator', () => {
|
|||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: {},
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
const migratedDoc = migrator.migrate(originalDoc);
|
||||
expect(_.get(originalDoc, 'attributes.name')).toBeUndefined();
|
||||
expect(_.get(migratedDoc, 'attributes.name')).toBe('Mike');
|
||||
});
|
||||
|
||||
it('migrates root properties', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'acl',
|
||||
migrations: {
|
||||
'2.3.5': setAttr('acl', 'admins-only, sucka!'),
|
||||
},
|
||||
}),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
const actual = migrator.migrate({
|
||||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Tyler' },
|
||||
acl: 'anyone',
|
||||
migrationVersion: {},
|
||||
} as SavedObjectUnsanitizedDoc);
|
||||
expect(actual).toEqual({
|
||||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Tyler' },
|
||||
migrationVersion: { acl: '2.3.5' },
|
||||
acl: 'admins-only, sucka!',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not apply migrations to unrelated docs', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
|
@ -246,7 +222,7 @@ describe('DocumentMigrator', () => {
|
|||
id: 'me',
|
||||
type: 'user',
|
||||
attributes: { name: 'Tyler' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'me',
|
||||
|
@ -255,7 +231,7 @@ describe('DocumentMigrator', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('assumes documents w/ undefined migrationVersion and correct coreMigrationVersion are up to date', () => {
|
||||
it('assumes documents w/ undefined typeMigrationVersion and correct coreMigrationVersion are up to date', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
|
@ -292,11 +268,8 @@ describe('DocumentMigrator', () => {
|
|||
type: 'user',
|
||||
attributes: { name: 'Tyler' },
|
||||
bbb: 'Shazm',
|
||||
migrationVersion: {
|
||||
user: '1.0.0',
|
||||
bbb: '2.3.4',
|
||||
},
|
||||
coreMigrationVersion: kibanaVersion,
|
||||
typeMigrationVersion: '1.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -317,17 +290,19 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: { dog: '1.2.3' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.2.3',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie', b: 'B', c: 'C' },
|
||||
migrationVersion: { dog: '2.0.1' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.1',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects docs with a migrationVersion[type] for a type that does not have any migrations defined', () => {
|
||||
it('rejects docs with a typeMigrationVersion for a type that does not have any migrations defined', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
});
|
||||
|
@ -337,14 +312,15 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: { dog: '10.2.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '10.2.0',
|
||||
})
|
||||
).toThrow(
|
||||
/Document "smelly" has property "dog" which belongs to a more recent version of Kibana \[10\.2\.0\]\. The last known version is \[undefined\]/i
|
||||
/Document "smelly" belongs to a more recent version of Kibana \[10\.2\.0\] when the last known version is \[undefined\]/i
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects docs with a migrationVersion[type] for a type that does not have a migration >= that version defined', () => {
|
||||
it('rejects docs with a typeMigrationVersion for a type that does not have a migration >= that version defined', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
|
@ -360,10 +336,10 @@ describe('DocumentMigrator', () => {
|
|||
id: 'fleabag',
|
||||
type: 'dawg',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: { dawg: '1.2.4' },
|
||||
typeMigrationVersion: '1.2.4',
|
||||
})
|
||||
).toThrow(
|
||||
/Document "fleabag" has property "dawg" which belongs to a more recent version of Kibana \[1\.2\.4\]\. The last known version is \[1\.2\.3\]/i
|
||||
/Document "fleabag" belongs to a more recent version of Kibana \[1\.2\.4\]\ when the last known version is \[1\.2\.3\]/i
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -421,47 +397,43 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: { dog: '1.2.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.2.0',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie', a: 1, b: 2, c: 3 },
|
||||
migrationVersion: { dog: '10.0.1' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '10.0.1',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows props to be added', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'animal',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('animal', (name: string) => `Animal: ${name}`),
|
||||
},
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'2.2.4': setAttr('animal', 'Doggie'),
|
||||
},
|
||||
{
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'2.2.4': setAttr('animal', 'Doggie'),
|
||||
},
|
||||
}
|
||||
),
|
||||
}),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: { dog: '1.2.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.2.0',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
animal: 'Animal: Doggie',
|
||||
migrationVersion: { animal: '1.0.0', dog: '2.2.4' },
|
||||
animal: 'Doggie',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.2.4',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -482,13 +454,15 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { title: 'Title: Name: Callie' },
|
||||
migrationVersion: { dog: '1.0.2' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.2',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -515,23 +489,25 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Callie' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'cat',
|
||||
attributes: { name: 'Kitty Callie' },
|
||||
migrationVersion: { dog: '2.2.4', cat: '1.0.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('disallows updating a migrationVersion prop to a lower version', () => {
|
||||
it('disallows updating a typeMigrationVersion prop to a lower version', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.foo', '3.2.1'),
|
||||
'4.5.7': setAttr('typeMigrationVersion', '3.2.1'),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
@ -541,20 +517,21 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'cat',
|
||||
attributes: { name: 'Boo' },
|
||||
migrationVersion: { foo: '4.5.6' },
|
||||
typeMigrationVersion: '4.5.6',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
})
|
||||
).toThrow(
|
||||
/Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to 3.2.1./
|
||||
/Migration "cat v4.5.7" attempted to downgrade "typeMigrationVersion" from 4.5.6 to 3.2.1./
|
||||
);
|
||||
});
|
||||
|
||||
it('disallows removing a migrationVersion prop', () => {
|
||||
it('disallows removing a typeMigrationVersion prop', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion', {}),
|
||||
'4.5.7': setAttr('typeMigrationVersion', undefined),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
@ -564,45 +541,21 @@ describe('DocumentMigrator', () => {
|
|||
id: 'smelly',
|
||||
type: 'cat',
|
||||
attributes: { name: 'Boo' },
|
||||
migrationVersion: { foo: '4.5.6' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '4.5.6',
|
||||
})
|
||||
).toThrow(
|
||||
/Migration "cat v 1.0.0" attempted to downgrade "migrationVersion.foo" from 4.5.6 to undefined./
|
||||
/Migration "cat v4.5.7" attempted to downgrade "typeMigrationVersion" from 4.5.6 to undefined./
|
||||
);
|
||||
});
|
||||
|
||||
it('allows adding props to migrationVersion', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'cat',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.foo', '5.6.7'),
|
||||
},
|
||||
}),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'cat',
|
||||
attributes: { name: 'Boo' },
|
||||
migrationVersion: {},
|
||||
});
|
||||
expect(actual).toEqual({
|
||||
id: 'smelly',
|
||||
type: 'cat',
|
||||
attributes: { name: 'Boo' },
|
||||
migrationVersion: { cat: '1.0.0', foo: '5.6.7' },
|
||||
});
|
||||
});
|
||||
|
||||
it('logs the original error and throws a transform error if a document transform fails', () => {
|
||||
const log = mockLogger;
|
||||
const failedDoc = {
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
|
@ -648,7 +601,7 @@ describe('DocumentMigrator', () => {
|
|||
id: 'joker',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
migrator.migrate(doc);
|
||||
expect(loggingSystemMock.collect(mockLoggerFactory).info[0][0]).toEqual(logTestMsg);
|
||||
|
@ -680,7 +633,7 @@ describe('DocumentMigrator', () => {
|
|||
migrations: {
|
||||
'9.0.0': (doc: SavedObjectUnsanitizedDoc) => doc,
|
||||
},
|
||||
convertToMultiNamespaceTypeVersion: '11.0.0', // this results in reference transforms getting added to other types, but does not increase the migrationVersion of those types
|
||||
convertToMultiNamespaceTypeVersion: '11.0.0', // this results in reference transforms getting added to other types, but does not increase the typeMigrationVersion of those types
|
||||
}
|
||||
),
|
||||
});
|
||||
|
@ -694,12 +647,12 @@ describe('DocumentMigrator', () => {
|
|||
});
|
||||
|
||||
describe('conversion to multi-namespace type', () => {
|
||||
it('assumes documents w/ undefined migrationVersion and correct coreMigrationVersion are up to date', () => {
|
||||
it('assumes documents w/ undefined typeMigrationVersion and correct coreMigrationVersion are up to date', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
{ name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' }
|
||||
// no migration transforms are defined, the migrationVersion will be derived from 'convertToMultiNamespaceTypeVersion'
|
||||
// no migration transforms are defined, the typeMigrationVersion will be derived from 'convertToMultiNamespaceTypeVersion'
|
||||
),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
|
@ -715,8 +668,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'mischievous',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Ann' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
coreMigrationVersion: kibanaVersion,
|
||||
typeMigrationVersion: '1.0.0',
|
||||
// there is no 'namespaces' field because no transforms were applied; this scenario is contrived for a clean test case but is not indicative of a real-world scenario
|
||||
},
|
||||
]);
|
||||
|
@ -727,7 +680,7 @@ describe('DocumentMigrator', () => {
|
|||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
{ name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' }
|
||||
// no migration transforms are defined, the migrationVersion will be derived from 'convertToMultiNamespaceTypeVersion'
|
||||
// no migration transforms are defined, the typeMigrationVersion will be derived from 'convertToMultiNamespaceTypeVersion'
|
||||
),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
|
@ -735,8 +688,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'mischievous',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Ann' },
|
||||
migrationVersion: { dog: '0.1.0' },
|
||||
coreMigrationVersion: '2.0.0',
|
||||
coreMigrationVersion: '20.0.0',
|
||||
typeMigrationVersion: '0.1.0',
|
||||
} as SavedObjectUnsanitizedDoc;
|
||||
const actual = migrator.migrateAndConvert(obj);
|
||||
expect(actual).toEqual([
|
||||
|
@ -744,8 +697,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'mischievous',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Ann' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
coreMigrationVersion: '2.0.0',
|
||||
coreMigrationVersion: '20.0.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespaces: ['default'],
|
||||
},
|
||||
]);
|
||||
|
@ -755,8 +708,8 @@ describe('DocumentMigrator', () => {
|
|||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
{ name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' },
|
||||
{ name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '1.0.0' }
|
||||
{ name: 'dog', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '8.8.0' },
|
||||
{ name: 'toy', namespaceType: 'multiple', convertToMultiNamespaceTypeVersion: '8.8.0' }
|
||||
),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
|
@ -764,9 +717,10 @@ describe('DocumentMigrator', () => {
|
|||
id: 'cowardly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Leslie' },
|
||||
migrationVersion: {},
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
namespace: 'foo-namespace',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
const actual = migrator.migrate(obj);
|
||||
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
|
||||
|
@ -775,13 +729,14 @@ describe('DocumentMigrator', () => {
|
|||
type: 'dog',
|
||||
attributes: { name: 'Leslie' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.8.0',
|
||||
namespace: 'foo-namespace',
|
||||
// there is no 'namespaces' field because no conversion transform was applied; this scenario is contrived for a clean test case but is not indicative of a real-world scenario
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep the same `migrationVersion` when the conversion transforms are skipped', () => {
|
||||
it('should keep the same `typeMigrationVersion` when the conversion transforms are skipped', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
|
@ -798,7 +753,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'cowardly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Leslie' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
namespace: 'foo-namespace',
|
||||
};
|
||||
|
@ -808,14 +764,67 @@ describe('DocumentMigrator', () => {
|
|||
id: 'cowardly',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Leslie' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
coreMigrationVersion: '3.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespace: 'foo-namespace',
|
||||
// there is no 'namespaces' field because no conversion transform was applied; this scenario is contrived for a clean test case but is not indicative of a real-world scenario
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly applies core transforms', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
{
|
||||
name: 'dog',
|
||||
namespaceType: 'single',
|
||||
migrations: { '1.0.0': (doc) => doc },
|
||||
},
|
||||
{ name: 'toy', namespaceType: 'multiple' }
|
||||
),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
const obj = {
|
||||
id: 'bad',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
};
|
||||
|
||||
it('in the default space', () => {
|
||||
const actual = migrator.migrateAndConvert(obj);
|
||||
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
|
||||
expect(actual).toEqual([
|
||||
{
|
||||
id: 'bad',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('in a non-default space', () => {
|
||||
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
|
||||
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
|
||||
expect(actual).toEqual([
|
||||
{
|
||||
id: 'bad',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespace: 'foo-namespace',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly applies reference transforms', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
|
@ -829,7 +838,7 @@ describe('DocumentMigrator', () => {
|
|||
id: 'bad',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }],
|
||||
};
|
||||
|
||||
|
@ -842,7 +851,7 @@ describe('DocumentMigrator', () => {
|
|||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -857,7 +866,7 @@ describe('DocumentMigrator', () => {
|
|||
type: 'dog',
|
||||
attributes: { name: 'Sweet Peach' },
|
||||
references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
namespace: 'foo-namespace',
|
||||
},
|
||||
]);
|
||||
|
@ -878,7 +887,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'loud',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Wally' },
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
|
||||
it('in the default space', () => {
|
||||
|
@ -889,8 +899,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'loud',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Wally' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespaces: ['default'],
|
||||
},
|
||||
]);
|
||||
|
@ -905,8 +915,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'uuidv5',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Wally' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespaces: ['foo-namespace'],
|
||||
originId: 'loud',
|
||||
},
|
||||
|
@ -920,14 +930,14 @@ describe('DocumentMigrator', () => {
|
|||
targetId: 'uuidv5',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { [LEGACY_URL_ALIAS_TYPE]: '0.1.2' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '0.1.2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly applies reference and conversion transforms', () => {
|
||||
describe('correctly applies core, reference, and conversion transforms', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
|
@ -952,9 +962,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'cute',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Too' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespaces: ['default'],
|
||||
},
|
||||
]);
|
||||
|
@ -980,9 +990,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'uuidv5',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Too' },
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '1.0.0',
|
||||
namespaces: ['foo-namespace'],
|
||||
originId: 'cute',
|
||||
},
|
||||
|
@ -996,14 +1006,14 @@ describe('DocumentMigrator', () => {
|
|||
targetId: 'uuidv5',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { [LEGACY_URL_ALIAS_TYPE]: '0.1.2' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '0.1.2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly applies reference and migration transforms', () => {
|
||||
describe('correctly applies core, reference, and migration transforms', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
|
@ -1037,9 +1047,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'sleepy',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Patches', age: '11', color: 'tri-color' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -1053,9 +1063,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'sleepy',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Patches', age: '11', color: 'tri-color' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespace: 'foo-namespace',
|
||||
},
|
||||
]);
|
||||
|
@ -1069,7 +1079,7 @@ describe('DocumentMigrator', () => {
|
|||
name: 'dog',
|
||||
namespaceType: 'multiple',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.dog', '2.0.0'),
|
||||
'1.0.0': setAttr('typeMigrationVersion', '2.0.0'),
|
||||
'2.0.0': (doc) => doc, // noop
|
||||
},
|
||||
convertToMultiNamespaceTypeVersion: '1.0.0', // the conversion transform occurs before the migration transform above
|
||||
|
@ -1080,7 +1090,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'hungry',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Remy' },
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '',
|
||||
};
|
||||
|
||||
it('in the default space', () => {
|
||||
|
@ -1091,8 +1102,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'hungry',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Remy' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespaces: ['default'],
|
||||
},
|
||||
]);
|
||||
|
@ -1107,8 +1118,8 @@ describe('DocumentMigrator', () => {
|
|||
id: 'uuidv5',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Remy' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespaces: ['foo-namespace'],
|
||||
originId: 'hungry',
|
||||
},
|
||||
|
@ -1122,14 +1133,14 @@ describe('DocumentMigrator', () => {
|
|||
targetId: 'uuidv5',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { [LEGACY_URL_ALIAS_TYPE]: '0.1.2' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '0.1.2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('correctly applies reference, conversion, and migration transforms', () => {
|
||||
describe('correctly applies core, reference, conversion, and migration transforms', () => {
|
||||
const migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry(
|
||||
|
@ -1137,7 +1148,7 @@ describe('DocumentMigrator', () => {
|
|||
name: 'dog',
|
||||
namespaceType: 'multiple',
|
||||
migrations: {
|
||||
'1.0.0': setAttr('migrationVersion.dog', '2.0.0'),
|
||||
'1.0.0': setAttr('typeMigrationVersion', '2.0.0'),
|
||||
'2.0.0': (doc) => doc, // noop
|
||||
},
|
||||
convertToMultiNamespaceTypeVersion: '1.0.0',
|
||||
|
@ -1162,9 +1173,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'pretty',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sasha' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespaces: ['default'],
|
||||
},
|
||||
]);
|
||||
|
@ -1190,9 +1201,9 @@ describe('DocumentMigrator', () => {
|
|||
id: 'uuidv5',
|
||||
type: 'dog',
|
||||
attributes: { name: 'Sasha' },
|
||||
migrationVersion: { dog: '2.0.0' },
|
||||
references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '2.0.0',
|
||||
namespaces: ['foo-namespace'],
|
||||
originId: 'pretty',
|
||||
},
|
||||
|
@ -1206,13 +1217,117 @@ describe('DocumentMigrator', () => {
|
|||
targetId: 'uuidv5',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { [LEGACY_URL_ALIAS_TYPE]: '0.1.2' },
|
||||
coreMigrationVersion: '1.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '0.1.2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('`typeMigrationVersion` core migration', () => {
|
||||
let migrator: DocumentMigrator;
|
||||
let noop: jest.MockedFunction<(doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc>;
|
||||
|
||||
beforeEach(() => {
|
||||
noop = jest.fn((doc) => doc);
|
||||
migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
migrations: {
|
||||
'1.0.0': noop,
|
||||
},
|
||||
}),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
});
|
||||
|
||||
it('migrates to `typeMigrationVersion`', () => {
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: { dog: '1.0.0' },
|
||||
});
|
||||
expect(actual).toHaveProperty('typeMigrationVersion', '1.0.0');
|
||||
});
|
||||
|
||||
it('ignores unrelated versions', () => {
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: {
|
||||
dog: '1.0.0',
|
||||
cat: '2.0.0',
|
||||
},
|
||||
});
|
||||
expect(actual).toHaveProperty('typeMigrationVersion', '1.0.0');
|
||||
});
|
||||
|
||||
it('removes `migrationVersion` property', () => {
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: {
|
||||
dog: '1.0.0',
|
||||
cat: '2.0.0',
|
||||
},
|
||||
});
|
||||
expect(actual).not.toHaveProperty('migrationVersion');
|
||||
});
|
||||
|
||||
it('migrates to the latest on missing version', () => {
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.7.0',
|
||||
});
|
||||
expect(noop).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ typeMigrationVersion: '' }),
|
||||
expect.anything()
|
||||
);
|
||||
expect(actual).toHaveProperty('typeMigrationVersion', '1.0.0');
|
||||
});
|
||||
|
||||
it('does not migrate if there is no `migrationVersion`', () => {
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
coreMigrationVersion: '8.7.0',
|
||||
});
|
||||
expect(noop).not.toHaveBeenCalled();
|
||||
expect(actual).toHaveProperty('coreMigrationVersion', '8.8.0');
|
||||
expect(actual).toHaveProperty('typeMigrationVersion', '1.0.0');
|
||||
expect(actual).not.toHaveProperty('migrationVersion');
|
||||
});
|
||||
|
||||
it('does not add `typeMigrationVersion` if there are no migrations', () => {
|
||||
migrator = new DocumentMigrator({
|
||||
...testOpts(),
|
||||
typeRegistry: createRegistry({
|
||||
name: 'dog',
|
||||
}),
|
||||
});
|
||||
migrator.prepareMigrations();
|
||||
|
||||
const actual = migrator.migrate({
|
||||
id: 'smelly',
|
||||
type: 'dog',
|
||||
attributes: {},
|
||||
coreMigrationVersion: '8.7.0',
|
||||
});
|
||||
expect(noop).not.toHaveBeenCalled();
|
||||
expect(actual).toHaveProperty('coreMigrationVersion', '8.8.0');
|
||||
expect(actual).not.toHaveProperty('typeMigrationVersion');
|
||||
expect(actual).not.toHaveProperty('migrationVersion');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -41,28 +41,21 @@
|
|||
* given an empty migrationVersion property {} if no such property exists.
|
||||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
import _ from 'lodash';
|
||||
import Semver from 'semver';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common';
|
||||
import type {
|
||||
SavedObjectUnsanitizedDoc,
|
||||
ISavedObjectTypeRegistry,
|
||||
} from '@kbn/core-saved-objects-server';
|
||||
import type { ActiveMigrations, TransformResult } from './types';
|
||||
import type { ActiveMigrations } from './types';
|
||||
import { maxVersion } from './utils';
|
||||
import { buildActiveMigrations } from './build_active_migrations';
|
||||
import { DocumentMigratorPipeline } from './document_migrator_pipeline';
|
||||
|
||||
export type MigrateFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc;
|
||||
export type MigrateAndConvertFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc[];
|
||||
|
||||
type ApplyTransformsFn = (
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
options?: TransformOptions
|
||||
) => TransformResult;
|
||||
|
||||
interface TransformOptions {
|
||||
convertNamespaceTypes?: boolean;
|
||||
}
|
||||
|
@ -90,7 +83,6 @@ export interface VersionedTransformer {
|
|||
export class DocumentMigrator implements VersionedTransformer {
|
||||
private documentMigratorOptions: DocumentMigratorOptions;
|
||||
private migrations?: ActiveMigrations;
|
||||
private transformDoc?: ApplyTransformsFn;
|
||||
|
||||
/**
|
||||
* Creates an instance of DocumentMigrator.
|
||||
|
@ -143,12 +135,33 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
log,
|
||||
convertVersion,
|
||||
});
|
||||
this.transformDoc = buildDocumentTransform({
|
||||
kibanaVersion,
|
||||
migrations: this.migrations,
|
||||
});
|
||||
};
|
||||
|
||||
private transform(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
{ convertNamespaceTypes = false }: TransformOptions = {}
|
||||
) {
|
||||
if (!this.migrations) {
|
||||
throw new Error('Migrations are not ready. Make sure prepareMigrations is called first.');
|
||||
}
|
||||
|
||||
// Clone the document to prevent accidental mutations on the original data
|
||||
// Ex: Importing sample data that is cached at import level, migrations would
|
||||
// execute on mutated data the second time.
|
||||
const clonedDoc = _.cloneDeep(doc);
|
||||
const pipeline = new DocumentMigratorPipeline(
|
||||
clonedDoc,
|
||||
this.migrations,
|
||||
this.documentMigratorOptions.kibanaVersion,
|
||||
convertNamespaceTypes
|
||||
);
|
||||
pipeline.run();
|
||||
|
||||
const { document, additionalDocs } = pipeline;
|
||||
|
||||
return { document, additionalDocs };
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a document to the latest version.
|
||||
*
|
||||
|
@ -157,16 +170,9 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
* @memberof DocumentMigrator
|
||||
*/
|
||||
public migrate = (doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc => {
|
||||
if (!this.migrations || !this.transformDoc) {
|
||||
throw new Error('Migrations are not ready. Make sure prepareMigrations is called first.');
|
||||
}
|
||||
const { document } = this.transform(doc);
|
||||
|
||||
// Clone the document to prevent accidental mutations on the original data
|
||||
// Ex: Importing sample data that is cached at import level, migrations would
|
||||
// execute on mutated data the second time.
|
||||
const clonedDoc = _.cloneDeep(doc);
|
||||
const { transformedDoc } = this.transformDoc(clonedDoc);
|
||||
return transformedDoc;
|
||||
return document;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -178,364 +184,8 @@ export class DocumentMigrator implements VersionedTransformer {
|
|||
* @memberof DocumentMigrator
|
||||
*/
|
||||
public migrateAndConvert = (doc: SavedObjectUnsanitizedDoc): SavedObjectUnsanitizedDoc[] => {
|
||||
if (!this.migrations || !this.transformDoc) {
|
||||
throw new Error('Migrations are not ready. Make sure prepareMigrations is called first.');
|
||||
}
|
||||
const { document, additionalDocs } = this.transform(doc, { convertNamespaceTypes: true });
|
||||
|
||||
// Clone the document to prevent accidental mutations on the original data
|
||||
// Ex: Importing sample data that is cached at import level, migrations would
|
||||
// execute on mutated data the second time.
|
||||
const clonedDoc = _.cloneDeep(doc);
|
||||
const { transformedDoc, additionalDocs } = this.transformDoc(clonedDoc, {
|
||||
convertNamespaceTypes: true,
|
||||
});
|
||||
return [transformedDoc, ...additionalDocs];
|
||||
return [document, ...additionalDocs];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function which migrates and validates any document that is passed to it.
|
||||
*/
|
||||
function buildDocumentTransform({
|
||||
kibanaVersion,
|
||||
migrations,
|
||||
}: {
|
||||
kibanaVersion: string;
|
||||
migrations: ActiveMigrations;
|
||||
}): ApplyTransformsFn {
|
||||
return function transformAndValidate(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
options: TransformOptions = {}
|
||||
) {
|
||||
validateCoreMigrationVersion(doc, kibanaVersion);
|
||||
|
||||
const { convertNamespaceTypes = false } = options;
|
||||
let transformedDoc: SavedObjectUnsanitizedDoc;
|
||||
let additionalDocs: SavedObjectUnsanitizedDoc[] = [];
|
||||
if (doc.migrationVersion) {
|
||||
const result = applyMigrations(doc, migrations, convertNamespaceTypes);
|
||||
transformedDoc = result.transformedDoc;
|
||||
additionalDocs = additionalDocs.concat(
|
||||
result.additionalDocs.map((x) => markAsUpToDate(x, migrations))
|
||||
);
|
||||
} else {
|
||||
transformedDoc = markAsUpToDate(doc, migrations);
|
||||
}
|
||||
|
||||
// In order to keep tests a bit more stable, we won't
|
||||
// tack on an empty migrationVersion to docs that have
|
||||
// no migrations defined.
|
||||
if (_.isEmpty(transformedDoc.migrationVersion)) {
|
||||
delete transformedDoc.migrationVersion;
|
||||
}
|
||||
|
||||
return { transformedDoc, additionalDocs };
|
||||
};
|
||||
}
|
||||
|
||||
function validateCoreMigrationVersion(doc: SavedObjectUnsanitizedDoc, kibanaVersion: string) {
|
||||
const { id, coreMigrationVersion: docVersion } = doc;
|
||||
if (!docVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We verify that the object's coreMigrationVersion is valid, and that it is not greater than the version supported by Kibana.
|
||||
// If we have a coreMigrationVersion and the kibanaVersion is smaller than it or does not exist, we are dealing with a document that
|
||||
// belongs to a future Kibana / plugin version.
|
||||
if (!Semver.valid(docVersion)) {
|
||||
throw Boom.badData(
|
||||
`Document "${id}" has an invalid "coreMigrationVersion" [${docVersion}]. This must be a semver value.`,
|
||||
doc
|
||||
);
|
||||
}
|
||||
|
||||
if (docVersion && Semver.gt(docVersion, kibanaVersion)) {
|
||||
throw Boom.badData(
|
||||
`Document "${id}" has a "coreMigrationVersion" which belongs to a more recent version` +
|
||||
` of Kibana [${docVersion}]. The current version is [${kibanaVersion}].`,
|
||||
doc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyMigrations(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrations: ActiveMigrations,
|
||||
convertNamespaceTypes: boolean
|
||||
) {
|
||||
let additionalDocs: SavedObjectUnsanitizedDoc[] = [];
|
||||
while (true) {
|
||||
const prop = nextUnmigratedProp(doc, migrations, convertNamespaceTypes);
|
||||
|
||||
if (!prop) {
|
||||
// Ensure that newly created documents have an up-to-date coreMigrationVersion field
|
||||
const { coreMigrationVersion = getLatestCoreVersion(doc, migrations), ...transformedDoc } =
|
||||
doc;
|
||||
|
||||
return {
|
||||
transformedDoc: {
|
||||
...transformedDoc,
|
||||
...(coreMigrationVersion ? { coreMigrationVersion } : {}),
|
||||
},
|
||||
additionalDocs,
|
||||
};
|
||||
}
|
||||
const result = migrateProp(doc, prop, migrations, convertNamespaceTypes);
|
||||
doc = result.transformedDoc;
|
||||
additionalDocs = [...additionalDocs, ...result.additionalDocs];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the doc's props, handling the special case of "type".
|
||||
*/
|
||||
function props(doc: SavedObjectUnsanitizedDoc) {
|
||||
return Object.keys(doc).concat(doc.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the prop version in a saved object document or in our latest migrations.
|
||||
*/
|
||||
function propVersion(doc: SavedObjectUnsanitizedDoc, prop: string) {
|
||||
return doc.migrationVersion && (doc as any).migrationVersion[prop];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the doc's migrationVersion to be the most recent version
|
||||
*/
|
||||
function markAsUpToDate(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) {
|
||||
const { coreMigrationVersion = getLatestCoreVersion(doc, migrations), ...rest } = doc;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
migrationVersion: props(doc).reduce((acc, prop) => {
|
||||
const version = maxVersion(
|
||||
migrations[prop]?.latestVersion.migrate,
|
||||
migrations[prop]?.latestVersion.convert
|
||||
);
|
||||
|
||||
return version ? set(acc, prop, version) : acc;
|
||||
}, {}),
|
||||
...(coreMigrationVersion ? { coreMigrationVersion } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a document has any pending transforms that should be applied based on its coreMigrationVersion field.
|
||||
* Currently, only reference transforms qualify.
|
||||
*/
|
||||
function hasPendingCoreTransform(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrations: ActiveMigrations,
|
||||
prop: string
|
||||
) {
|
||||
if (!migrations.hasOwnProperty(prop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const latestCoreMigrationVersion = migrations[prop].latestVersion.reference;
|
||||
const { coreMigrationVersion } = doc;
|
||||
return (
|
||||
latestCoreMigrationVersion &&
|
||||
(!coreMigrationVersion || Semver.gt(latestCoreMigrationVersion, coreMigrationVersion))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a document has any pending conversion transforms that should be applied.
|
||||
* Currently, only reference transforms qualify.
|
||||
*/
|
||||
function hasPendingConversionTransform(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrations: ActiveMigrations,
|
||||
prop: string
|
||||
) {
|
||||
if (!migrations.hasOwnProperty(prop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const latestVersion = migrations[prop].latestVersion.convert;
|
||||
const migrationVersion = doc.migrationVersion?.[prop];
|
||||
|
||||
return latestVersion && (!migrationVersion || Semver.gt(latestVersion, migrationVersion));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a document has any pending transforms that should be applied based on its coreMigrationVersion field.
|
||||
* Currently, only reference transforms qualify.
|
||||
*/
|
||||
function hasPendingMigrationTransform(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrations: ActiveMigrations,
|
||||
prop: string
|
||||
) {
|
||||
if (!migrations.hasOwnProperty(prop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const latestVersion = migrations[prop].latestVersion.migrate;
|
||||
const migrationVersion = doc.migrationVersion?.[prop];
|
||||
|
||||
return latestVersion && (!migrationVersion || Semver.gt(latestVersion, migrationVersion));
|
||||
}
|
||||
|
||||
function getLatestCoreVersion(doc: SavedObjectUnsanitizedDoc, migrations: ActiveMigrations) {
|
||||
let latestVersion: string | undefined;
|
||||
|
||||
for (const prop of props(doc)) {
|
||||
latestVersion = maxVersion(latestVersion, migrations[prop]?.latestVersion.reference);
|
||||
}
|
||||
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first unmigrated property in the specified document.
|
||||
*/
|
||||
function nextUnmigratedProp(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrations: ActiveMigrations,
|
||||
convertNamespaceTypes: boolean
|
||||
) {
|
||||
return props(doc).find((p) => {
|
||||
const latestMigrationVersion = maxVersion(
|
||||
migrations[p]?.latestVersion.migrate,
|
||||
migrations[p]?.latestVersion.convert
|
||||
);
|
||||
const docVersion = propVersion(doc, p);
|
||||
|
||||
// We verify that the version is not greater than the version supported by Kibana.
|
||||
// If we didn't, this would cause an infinite loop, as we'd be unable to migrate the property
|
||||
// but it would continue to show up as unmigrated.
|
||||
// If we have a docVersion and the latestMigrationVersion is smaller than it or does not exist,
|
||||
// we are dealing with a document that belongs to a future Kibana / plugin version.
|
||||
if (docVersion && (!latestMigrationVersion || Semver.gt(docVersion, latestMigrationVersion))) {
|
||||
throw Boom.badData(
|
||||
`Document "${doc.id}" has property "${p}" which belongs to a more recent` +
|
||||
` version of Kibana [${docVersion}]. The last known version is [${latestMigrationVersion}]`,
|
||||
doc
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
hasPendingMigrationTransform(doc, migrations, p) ||
|
||||
(convertNamespaceTypes && // If the object itself is up-to-date, check if its references are up-to-date too
|
||||
(hasPendingCoreTransform(doc, migrations, p) ||
|
||||
hasPendingConversionTransform(doc, migrations, p)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies any relevant migrations to the document for the specified property.
|
||||
*/
|
||||
function migrateProp(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
prop: string,
|
||||
migrations: ActiveMigrations,
|
||||
convertNamespaceTypes: boolean
|
||||
): TransformResult {
|
||||
const originalType = doc.type;
|
||||
let migrationVersion = _.clone(doc.migrationVersion) || {};
|
||||
let additionalDocs: SavedObjectUnsanitizedDoc[] = [];
|
||||
|
||||
for (const { version, transform, transformType } of applicableTransforms(
|
||||
doc,
|
||||
prop,
|
||||
migrations,
|
||||
convertNamespaceTypes
|
||||
)) {
|
||||
const result = transform(doc);
|
||||
doc = result.transformedDoc;
|
||||
additionalDocs = [...additionalDocs, ...result.additionalDocs];
|
||||
|
||||
if (transformType === 'reference') {
|
||||
// regardless of whether or not the reference transform was applied, update the object's coreMigrationVersion
|
||||
// this is needed to ensure that we don't have an endless migration loop
|
||||
doc.coreMigrationVersion = version;
|
||||
} else {
|
||||
migrationVersion = updateMigrationVersion(doc, migrationVersion, prop, version);
|
||||
doc.migrationVersion = _.clone(migrationVersion);
|
||||
}
|
||||
|
||||
if (doc.type !== originalType) {
|
||||
// the transform function changed the object's type; break out of the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { transformedDoc: doc, additionalDocs };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves any prop transforms that have not been applied to doc.
|
||||
*/
|
||||
function applicableTransforms(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
prop: string,
|
||||
migrations: ActiveMigrations,
|
||||
convertNamespaceTypes: boolean
|
||||
) {
|
||||
const minMigrationVersion = propVersion(doc, prop);
|
||||
const minCoreMigrationVersion = doc.coreMigrationVersion || '0.0.0';
|
||||
const { transforms } = migrations[prop];
|
||||
|
||||
return transforms
|
||||
.filter(
|
||||
({ transformType }) =>
|
||||
convertNamespaceTypes || !['convert', 'reference'].includes(transformType)
|
||||
)
|
||||
.filter(
|
||||
({ transformType, version }) =>
|
||||
!minMigrationVersion ||
|
||||
Semver.gt(
|
||||
version,
|
||||
transformType === 'reference' ? minCoreMigrationVersion : minMigrationVersion
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the document's migrationVersion, ensuring that the calling transform
|
||||
* has not mutated migrationVersion in an unsupported way.
|
||||
*/
|
||||
function updateMigrationVersion(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrationVersion: SavedObjectsMigrationVersion,
|
||||
prop: string,
|
||||
version: string
|
||||
) {
|
||||
assertNoDowngrades(doc, migrationVersion, prop, version);
|
||||
return {
|
||||
...(doc.migrationVersion || migrationVersion),
|
||||
[prop]: maxVersion(propVersion(doc, prop), version) ?? '0.0.0',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms that remove or downgrade migrationVersion properties are not allowed,
|
||||
* as this could get us into an infinite loop. So, we explicitly check for that here.
|
||||
*/
|
||||
function assertNoDowngrades(
|
||||
doc: SavedObjectUnsanitizedDoc,
|
||||
migrationVersion: SavedObjectsMigrationVersion,
|
||||
prop: string,
|
||||
version: string
|
||||
) {
|
||||
const docVersion = doc.migrationVersion;
|
||||
if (!docVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const downgrade = Object.keys(migrationVersion).find(
|
||||
(k) => !docVersion.hasOwnProperty(k) || Semver.lt(docVersion[k], migrationVersion[k])
|
||||
);
|
||||
|
||||
if (downgrade) {
|
||||
throw new Error(
|
||||
`Migration "${prop} v ${version}" attempted to ` +
|
||||
`downgrade "migrationVersion.${downgrade}" from ${migrationVersion[downgrade]} ` +
|
||||
`to ${docVersion[downgrade]}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import Semver from 'semver';
|
||||
import type { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server';
|
||||
import { ActiveMigrations, Transform, TransformType } from './types';
|
||||
import { maxVersion } from './utils';
|
||||
|
||||
function isGreater(a?: string, b?: string) {
|
||||
return !!a && (!b || Semver.gt(a, b));
|
||||
}
|
||||
|
||||
export class DocumentMigratorPipeline {
|
||||
additionalDocs = [] as SavedObjectUnsanitizedDoc[];
|
||||
|
||||
constructor(
|
||||
public document: SavedObjectUnsanitizedDoc,
|
||||
private migrations: ActiveMigrations,
|
||||
private kibanaVersion: string,
|
||||
private convertNamespaceTypes: boolean
|
||||
) {}
|
||||
|
||||
protected *getPipeline(): Generator<Transform> {
|
||||
while (this.hasPendingTransforms()) {
|
||||
const { type } = this.document;
|
||||
|
||||
for (const transform of this.getPendingTransforms()) {
|
||||
yield transform;
|
||||
|
||||
if (type !== this.document.type) {
|
||||
// In the initial implementation, all the transforms for the new type should be applied.
|
||||
// And at the same time, documents with `undefined` in `typeMigrationVersion` are treated as the most recent ones.
|
||||
// This is a workaround to get into the loop again and apply all the migrations for the new type.
|
||||
this.document.typeMigrationVersion = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private hasPendingTransforms() {
|
||||
const { coreMigrationVersion, typeMigrationVersion, type } = this.document;
|
||||
const latestVersion = this.migrations[type]?.latestVersion;
|
||||
|
||||
if (isGreater(latestVersion?.core, coreMigrationVersion)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeMigrationVersion == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
isGreater(latestVersion?.migrate, typeMigrationVersion) ||
|
||||
(this.convertNamespaceTypes && isGreater(latestVersion?.convert, typeMigrationVersion)) ||
|
||||
(this.convertNamespaceTypes && isGreater(latestVersion?.reference, coreMigrationVersion))
|
||||
);
|
||||
}
|
||||
|
||||
private getPendingTransforms() {
|
||||
const { transforms } = this.migrations[this.document.type];
|
||||
|
||||
return transforms.filter((transform) => this.isPendingTransform(transform));
|
||||
}
|
||||
|
||||
private isPendingTransform({ transformType, version }: Transform) {
|
||||
const { coreMigrationVersion, typeMigrationVersion, type } = this.document;
|
||||
const latestVersion = this.migrations[type]?.latestVersion;
|
||||
|
||||
switch (transformType) {
|
||||
case TransformType.Core:
|
||||
return isGreater(version, coreMigrationVersion);
|
||||
case TransformType.Reference:
|
||||
return (
|
||||
(this.convertNamespaceTypes || isGreater(latestVersion.core, coreMigrationVersion)) &&
|
||||
isGreater(version, coreMigrationVersion)
|
||||
);
|
||||
case TransformType.Convert:
|
||||
return (
|
||||
typeMigrationVersion != null &&
|
||||
this.convertNamespaceTypes &&
|
||||
isGreater(version, typeMigrationVersion)
|
||||
);
|
||||
case TransformType.Migrate:
|
||||
return typeMigrationVersion != null && isGreater(version, typeMigrationVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the object's core version is valid and not greater than the current Kibana version.
|
||||
* Hence, the object does not belong to a more recent version of Kibana.
|
||||
*/
|
||||
private assertValidity() {
|
||||
const { id, coreMigrationVersion } = this.document;
|
||||
if (!coreMigrationVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Semver.valid(coreMigrationVersion)) {
|
||||
throw Boom.badData(
|
||||
`Document "${id}" has an invalid "coreMigrationVersion" [${coreMigrationVersion}]. This must be a semver value.`,
|
||||
this.document
|
||||
);
|
||||
}
|
||||
|
||||
if (Semver.gt(coreMigrationVersion, this.kibanaVersion)) {
|
||||
throw Boom.badData(
|
||||
`Document "${id}" has a "coreMigrationVersion" which belongs to a more recent version` +
|
||||
` of Kibana [${coreMigrationVersion}]. The current version is [${this.kibanaVersion}].`,
|
||||
this.document
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the document version is not greater than the version supported by Kibana.
|
||||
* If we have a document with some version and no migrations available for this type,
|
||||
* the document belongs to a future version.
|
||||
*/
|
||||
private assertCompatibility() {
|
||||
const { id, type, typeMigrationVersion: currentVersion } = this.document;
|
||||
const latestVersion = maxVersion(
|
||||
this.migrations[type]?.latestVersion.migrate,
|
||||
this.migrations[type]?.latestVersion.convert
|
||||
);
|
||||
|
||||
if (isGreater(currentVersion, latestVersion)) {
|
||||
throw Boom.badData(
|
||||
`Document "${id}" belongs to a more recent version of Kibana [${currentVersion}] when the last known version is [${latestVersion}].`,
|
||||
this.document
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms that remove or downgrade `typeMigrationVersion` properties are not allowed,
|
||||
* as this could get us into an infinite loop. So, we explicitly check for that here.
|
||||
*/
|
||||
private assertUpgrade({ transformType, version }: Transform, previousVersion?: string) {
|
||||
if ([TransformType.Core, TransformType.Reference].includes(transformType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { typeMigrationVersion: currentVersion, type } = this.document;
|
||||
|
||||
if (isGreater(previousVersion, currentVersion)) {
|
||||
throw new Error(
|
||||
`Migration "${type} v${version}" attempted to downgrade "typeMigrationVersion" from ${previousVersion} to ${currentVersion}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private bumpVersion({ transformType, version }: Transform) {
|
||||
this.document = {
|
||||
...this.document,
|
||||
...([TransformType.Core, TransformType.Reference].includes(transformType)
|
||||
? { coreMigrationVersion: maxVersion(this.document.coreMigrationVersion, version) }
|
||||
: { typeMigrationVersion: maxVersion(this.document.typeMigrationVersion, version) }),
|
||||
};
|
||||
}
|
||||
|
||||
private ensureVersion({
|
||||
coreMigrationVersion: currentCoreMigrationVersion,
|
||||
typeMigrationVersion: currentTypeMigrationVersion,
|
||||
...document
|
||||
}: SavedObjectUnsanitizedDoc) {
|
||||
const { type } = document;
|
||||
const latestVersion = this.migrations[type]?.latestVersion;
|
||||
const coreMigrationVersion =
|
||||
currentCoreMigrationVersion || maxVersion(latestVersion?.core, latestVersion?.reference);
|
||||
const typeMigrationVersion =
|
||||
currentTypeMigrationVersion || maxVersion(latestVersion?.migrate, latestVersion?.convert);
|
||||
|
||||
return {
|
||||
...document,
|
||||
...(coreMigrationVersion ? { coreMigrationVersion } : {}),
|
||||
...(typeMigrationVersion ? { typeMigrationVersion } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
run(): void {
|
||||
this.assertValidity();
|
||||
|
||||
for (const transform of this.getPipeline()) {
|
||||
const { typeMigrationVersion: previousVersion } = this.document;
|
||||
const { additionalDocs, transformedDoc } = transform.transform(this.document);
|
||||
this.document = transformedDoc;
|
||||
this.additionalDocs.push(...additionalDocs.map((document) => this.ensureVersion(document)));
|
||||
|
||||
this.assertUpgrade(transform, previousVersion);
|
||||
this.bumpVersion(transform);
|
||||
}
|
||||
|
||||
this.assertCompatibility();
|
||||
|
||||
this.document = this.ensureVersion(this.document);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,20 @@ import {
|
|||
LEGACY_URL_ALIAS_TYPE,
|
||||
LegacyUrlAlias,
|
||||
} from '@kbn/core-saved-objects-base-server-internal';
|
||||
import { migrations as coreMigrationsMap } from './migrations';
|
||||
import { type Transform, TransformType } from './types';
|
||||
|
||||
/**
|
||||
* Returns all available core transforms for all object types.
|
||||
*/
|
||||
export function getCoreTransforms(): Transform[] {
|
||||
return Object.entries(coreMigrationsMap).map<Transform>(([version, transform]) => ({
|
||||
version,
|
||||
transform,
|
||||
transformType: TransformType.Core,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all applicable conversion transforms for a given object type.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TransformFn } from '../types';
|
||||
import { transformMigrationVersion } from './transform_migration_version';
|
||||
|
||||
export const migrations = {
|
||||
'8.8.0': transformMigrationVersion,
|
||||
} as Record<string, TransformFn>;
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { transformMigrationVersion } from './transform_migration_version';
|
||||
|
||||
describe('transformMigrationVersion', () => {
|
||||
it('should extract the correct version from the `migrationVersion` property', () => {
|
||||
expect(
|
||||
transformMigrationVersion({
|
||||
id: 'a',
|
||||
attributes: {},
|
||||
type: 'something',
|
||||
migrationVersion: {
|
||||
something: '1.0.0',
|
||||
previous: '2.0.0',
|
||||
},
|
||||
})
|
||||
).toHaveProperty('transformedDoc.typeMigrationVersion', '1.0.0');
|
||||
});
|
||||
|
||||
it('should remove the original `migrationVersion` property', () => {
|
||||
expect(
|
||||
transformMigrationVersion({
|
||||
id: 'a',
|
||||
attributes: {},
|
||||
type: 'something',
|
||||
migrationVersion: {
|
||||
something: '1.0.0',
|
||||
previous: '2.0.0',
|
||||
},
|
||||
})
|
||||
).not.toHaveProperty('transformedDoc.migrationVersion');
|
||||
});
|
||||
|
||||
it('should not add `typeMigrationVersion` if there is no `migrationVersion`', () => {
|
||||
expect(
|
||||
transformMigrationVersion({
|
||||
id: 'a',
|
||||
attributes: {},
|
||||
type: 'something',
|
||||
})
|
||||
).not.toHaveProperty('transformedDoc.typeMigrationVersion');
|
||||
});
|
||||
|
||||
it('should add empty `typeMigrationVersion` if there is no related value in `migrationVersion`', () => {
|
||||
expect(
|
||||
transformMigrationVersion({
|
||||
id: 'a',
|
||||
attributes: {},
|
||||
type: 'something',
|
||||
migrationVersion: {},
|
||||
})
|
||||
).toHaveProperty('transformedDoc.typeMigrationVersion', '');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TransformFn } from '../types';
|
||||
|
||||
export const transformMigrationVersion: TransformFn = ({ migrationVersion, ...doc }) => {
|
||||
return {
|
||||
transformedDoc: {
|
||||
...doc,
|
||||
...(migrationVersion ? { typeMigrationVersion: migrationVersion[doc.type] ?? '' } : {}),
|
||||
},
|
||||
additionalDocs: [],
|
||||
};
|
||||
};
|
|
@ -21,7 +21,7 @@ export interface ActiveMigrations {
|
|||
export interface TypeTransforms {
|
||||
/** Derived from the related transforms */
|
||||
latestVersion: Record<TransformType, string>;
|
||||
/** List of transforms registered for the type **/
|
||||
/** Ordered list of transforms registered for the type **/
|
||||
transforms: Transform[];
|
||||
}
|
||||
|
||||
|
@ -37,20 +37,29 @@ export interface Transform {
|
|||
transformType: TransformType;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two "migrationVersion" transform types:
|
||||
* * `migrate` - These transforms are defined and added by consumers using the type registry; each is applied to a single object type
|
||||
* based on an object's `migrationVersion[type]` field. These are applied during index migrations and document migrations.
|
||||
* * `convert` - These transforms are defined by core and added by consumers using the type registry; each is applied to a single object
|
||||
* type based on an object's `migrationVersion[type]` field. These are applied during index migrations, NOT document migrations.
|
||||
*
|
||||
* There is one "coreMigrationVersion" transform type:
|
||||
* * `reference` - These transforms are defined by core and added by consumers using the type registry; they are applied to all object
|
||||
* types based on their `coreMigrationVersion` field. These are applied during index migrations, NOT document migrations.
|
||||
*/
|
||||
export enum TransformType {
|
||||
Migrate = 'migrate',
|
||||
/**
|
||||
* These transforms are defined by core and added by consumers using the type registry; each is applied to a single object
|
||||
* type based on an object's `typeMigrationVersion` field. These are applied during index migrations, NOT document migrations.
|
||||
*/
|
||||
Convert = 'convert',
|
||||
|
||||
/**
|
||||
* These transforms are defined by core internally; they are applied to all object types based on their `coreMigrationVersion` field.
|
||||
* These are applied during index migrations and before any document migrations to guarantee that all documents have the most recent schema.
|
||||
*/
|
||||
Core = 'core',
|
||||
|
||||
/**
|
||||
* These transforms are defined and added by consumers using the type registry; each is applied to a single object type
|
||||
* based on an object's `typeMigrationVersion` field. These are applied during index migrations and document migrations.
|
||||
*/
|
||||
Migrate = 'migrate',
|
||||
|
||||
/**
|
||||
* These transforms are defined by core and added by consumers using the type registry; they are applied to all object
|
||||
* types based on their `coreMigrationVersion` field. These are applied during index migrations, NOT document migrations.
|
||||
*/
|
||||
Reference = 'reference',
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { type Transform, TransformType } from './types';
|
||||
import { transformComparator } from './utils';
|
||||
|
||||
describe('transformComparator', () => {
|
||||
const core1 = { version: '1.0.0', transformType: TransformType.Core } as Transform;
|
||||
const core5 = { version: '5.0.0', transformType: TransformType.Core } as Transform;
|
||||
const core6 = { version: '6.0.0', transformType: TransformType.Core } as Transform;
|
||||
const reference1 = { version: '1.0.0', transformType: TransformType.Reference } as Transform;
|
||||
const reference2 = { version: '2.0.0', transformType: TransformType.Reference } as Transform;
|
||||
const convert1 = { version: '1.0.0', transformType: TransformType.Convert } as Transform;
|
||||
const convert5 = { version: '5.0.0', transformType: TransformType.Convert } as Transform;
|
||||
const migrate1 = { version: '1.0.0', transformType: TransformType.Migrate } as Transform;
|
||||
const migrate2 = { version: '2.0.0', transformType: TransformType.Migrate } as Transform;
|
||||
const migrate5 = { version: '5.0.0', transformType: TransformType.Migrate } as Transform;
|
||||
|
||||
it.each`
|
||||
transforms | expected
|
||||
${[migrate1, reference1, core1, convert1]} | ${[core1, reference1, convert1, migrate1]}
|
||||
${[reference1, migrate1, core1, core5, core6, convert1]} | ${[core1, core5, core6, reference1, convert1, migrate1]}
|
||||
${[reference2, reference1, migrate1, core6, convert5]} | ${[core6, reference1, migrate1, reference2, convert5]}
|
||||
${[migrate5, convert5, core5, migrate2]} | ${[core5, migrate2, convert5, migrate5]}
|
||||
`('should sort transforms correctly', ({ transforms, expected }) => {
|
||||
expect(transforms.sort(transformComparator)).toEqual(expected);
|
||||
});
|
||||
});
|
|
@ -17,6 +17,13 @@ import { MigrationLogger } from '../core/migration_logger';
|
|||
import { TransformSavedObjectDocumentError } from '../core/transform_saved_object_document_error';
|
||||
import { type Transform, type TransformFn, TransformType } from './types';
|
||||
|
||||
const TRANSFORM_PRIORITY = [
|
||||
TransformType.Core,
|
||||
TransformType.Reference,
|
||||
TransformType.Convert,
|
||||
TransformType.Migrate,
|
||||
];
|
||||
|
||||
/**
|
||||
* If a specific transform function fails, this tacks on a bit of information
|
||||
* about the document and transform that caused the failure.
|
||||
|
@ -53,28 +60,28 @@ export function convertMigrationFunction(
|
|||
}
|
||||
|
||||
/**
|
||||
* Transforms are sorted in ascending order by version. One version may contain multiple transforms; 'reference' transforms always run
|
||||
* first, 'convert' transforms always run second, and 'migrate' transforms always run last. This is because:
|
||||
* Transforms are sorted in ascending order by version depending on their type:
|
||||
* - `core` transforms always run first no matter version;
|
||||
* - `reference` transforms have priority in case of the same version;
|
||||
* - `convert` transforms run after in case of the same version;
|
||||
* - 'migrate' transforms always run last.
|
||||
* This is because:
|
||||
* 1. 'convert' transforms get rid of the `namespace` field, which must be present for 'reference' transforms to function correctly.
|
||||
* 2. 'migrate' transforms are defined by the consumer, and may change the object type or migrationVersion which resets the migration loop
|
||||
* and could cause any remaining transforms for this version to be skipped.
|
||||
* 2. 'migrate' transforms are defined by the consumer, and may change the object type or `migrationVersion` which resets the migration loop
|
||||
* and could cause any remaining transforms for this version to be skipped.One version may contain multiple transforms.
|
||||
*/
|
||||
export function transformComparator(a: Transform, b: Transform) {
|
||||
const semver = Semver.compare(a.version, b.version);
|
||||
if (semver !== 0) {
|
||||
return semver;
|
||||
} else if (a.transformType !== b.transformType) {
|
||||
if (a.transformType === TransformType.Migrate) {
|
||||
return 1;
|
||||
} else if (b.transformType === TransformType.Migrate) {
|
||||
return -1;
|
||||
} else if (a.transformType === TransformType.Convert) {
|
||||
return 1;
|
||||
} else if (b.transformType === TransformType.Convert) {
|
||||
return -1;
|
||||
}
|
||||
const aPriority = TRANSFORM_PRIORITY.indexOf(a.transformType);
|
||||
const bPriority = TRANSFORM_PRIORITY.indexOf(b.transformType);
|
||||
|
||||
if (
|
||||
aPriority !== bPriority &&
|
||||
(a.transformType === TransformType.Core || b.transformType === TransformType.Core)
|
||||
) {
|
||||
return aPriority - bPriority;
|
||||
}
|
||||
return 0;
|
||||
|
||||
return Semver.compare(a.version, b.version) || aPriority - bPriority;
|
||||
}
|
||||
|
||||
export function maxVersion(a?: string, b?: string) {
|
||||
|
|
|
@ -369,41 +369,107 @@ describe('createInitialState', () => {
|
|||
logger: mockLogger.get(),
|
||||
}).outdatedDocumentsQuery
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bool": Object {
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Object {
|
||||
Object {
|
||||
"bool": Object {
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Array [
|
||||
Object {
|
||||
"term": Object {
|
||||
"type": "my_dashboard",
|
||||
},
|
||||
},
|
||||
"must_not": Object {
|
||||
"term": Object {
|
||||
"migrationVersion.my_dashboard": "7.10.1",
|
||||
Object {
|
||||
"bool": Object {
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Object {
|
||||
"exists": Object {
|
||||
"field": "migrationVersion",
|
||||
},
|
||||
},
|
||||
"must_not": Object {
|
||||
"term": Object {
|
||||
"migrationVersion.my_dashboard": "7.10.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must_not": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "migrationVersion",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"typeMigrationVersion": "7.10.1",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Object {
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Array [
|
||||
Object {
|
||||
"term": Object {
|
||||
"type": "my_viz",
|
||||
},
|
||||
},
|
||||
"must_not": Object {
|
||||
"term": Object {
|
||||
"migrationVersion.my_viz": "8.0.0",
|
||||
Object {
|
||||
"bool": Object {
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must": Object {
|
||||
"exists": Object {
|
||||
"field": "migrationVersion",
|
||||
},
|
||||
},
|
||||
"must_not": Object {
|
||||
"term": Object {
|
||||
"migrationVersion.my_viz": "8.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"must_not": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "migrationVersion",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('initializes the `discardUnknownObjects` flag to false if the flag is not provided in the config', () => {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import * as Option from 'fp-ts/Option';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { DocLinksServiceStart } from '@kbn/core-doc-links-server';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common';
|
||||
|
@ -44,12 +45,33 @@ export const createInitialState = ({
|
|||
docLinks: DocLinksServiceStart;
|
||||
logger: Logger;
|
||||
}): InitState => {
|
||||
const outdatedDocumentsQuery = {
|
||||
const outdatedDocumentsQuery: QueryDslQueryContainer = {
|
||||
bool: {
|
||||
should: Object.entries(migrationVersionPerType).map(([type, latestVersion]) => ({
|
||||
bool: {
|
||||
must: { term: { type } },
|
||||
must_not: { term: { [`migrationVersion.${type}`]: latestVersion } },
|
||||
must: [
|
||||
{ term: { type } },
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
must: { exists: { field: 'migrationVersion' } },
|
||||
must_not: { term: { [`migrationVersion.${type}`]: latestVersion } },
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: [
|
||||
{ exists: { field: 'migrationVersion' } },
|
||||
{ term: { typeMigrationVersion: latestVersion } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
|
|
|
@ -43,6 +43,7 @@ export const registerBulkCreateRoute = (
|
|||
version: schema.maybe(schema.string()),
|
||||
migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())),
|
||||
coreMigrationVersion: schema.maybe(schema.string()),
|
||||
typeMigrationVersion: schema.maybe(schema.string()),
|
||||
references: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
|
|
|
@ -43,6 +43,7 @@ export const registerCreateRoute = (
|
|||
attributes: schema.recordOf(schema.string(), schema.any()),
|
||||
migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())),
|
||||
coreMigrationVersion: schema.maybe(schema.string()),
|
||||
typeMigrationVersion: schema.maybe(schema.string()),
|
||||
references: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
|
@ -65,8 +66,14 @@ export const registerCreateRoute = (
|
|||
});
|
||||
const { type, id } = req.params;
|
||||
const { overwrite } = req.query;
|
||||
const { attributes, migrationVersion, coreMigrationVersion, references, initialNamespaces } =
|
||||
req.body;
|
||||
const {
|
||||
attributes,
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
references,
|
||||
initialNamespaces,
|
||||
} = req.body;
|
||||
|
||||
const usageStatsClient = coreUsageData.getClient();
|
||||
usageStatsClient.incrementSavedObjectsCreate({ request: req }).catch(() => {});
|
||||
|
@ -80,6 +87,7 @@ export const registerCreateRoute = (
|
|||
overwrite,
|
||||
migrationVersion,
|
||||
coreMigrationVersion,
|
||||
typeMigrationVersion,
|
||||
references,
|
||||
initialNamespaces,
|
||||
};
|
||||
|
|
|
@ -41,14 +41,14 @@ describe('importDashboards(req)', () => {
|
|||
type: 'dashboard',
|
||||
attributes: { panelJSON: '{}' },
|
||||
references: [],
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
{
|
||||
id: 'panel-01',
|
||||
type: 'visualization',
|
||||
attributes: { visState: '{}' },
|
||||
references: [],
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
],
|
||||
{ overwrite: false }
|
||||
|
@ -78,7 +78,7 @@ describe('importDashboards(req)', () => {
|
|||
type: 'dashboard',
|
||||
attributes: { panelJSON: '{}' },
|
||||
references: [],
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
},
|
||||
],
|
||||
{ overwrite: false }
|
||||
|
|
|
@ -14,15 +14,18 @@ export async function importDashboards(
|
|||
objects: SavedObject[],
|
||||
{ overwrite, exclude }: { overwrite: boolean; exclude: string[] }
|
||||
) {
|
||||
// The server assumes that documents with no migrationVersion are up to date.
|
||||
// That assumption enables Kibana and other API consumers to not have to build
|
||||
// up migrationVersion prior to creating new objects. But it means that imports
|
||||
// need to set migrationVersion to something other than undefined, so that imported
|
||||
// The server assumes that documents with no `typeMigrationVersion` are up to date.
|
||||
// That assumption enables Kibana and other API consumers to not have to determine
|
||||
// `typeMigrationVersion` prior to creating new objects. But it means that imports
|
||||
// need to set `typeMigrationVersion` to something other than undefined, so that imported
|
||||
// docs are not seen as automatically up-to-date.
|
||||
const docs = objects
|
||||
.filter((item) => !exclude.includes(item.type))
|
||||
// filter out any document version, if present
|
||||
.map(({ version, ...doc }) => ({ ...doc, migrationVersion: doc.migrationVersion || {} }));
|
||||
.map(({ version, ...doc }) => ({
|
||||
...doc,
|
||||
...(!doc.migrationVersion && !doc.typeMigrationVersion ? { typeMigrationVersion: '' } : {}),
|
||||
}));
|
||||
|
||||
const results = await savedObjectsClient.bulkCreate(docs, { overwrite });
|
||||
return { objects: results.saved_objects };
|
||||
|
|
|
@ -79,6 +79,7 @@ export interface SavedObjectsRawDocSource {
|
|||
namespace?: string;
|
||||
namespaces?: string[];
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
typeMigrationVersion?: string;
|
||||
updated_at?: string;
|
||||
created_at?: string;
|
||||
references?: SavedObjectReference[];
|
||||
|
@ -100,6 +101,7 @@ interface SavedObjectDoc<T = unknown> {
|
|||
namespaces?: string[];
|
||||
migrationVersion?: SavedObjectsMigrationVersion;
|
||||
coreMigrationVersion?: string;
|
||||
typeMigrationVersion?: string;
|
||||
version?: string;
|
||||
updated_at?: string;
|
||||
created_at?: string;
|
||||
|
|
|
@ -118,7 +118,7 @@ describe('migration v2', () => {
|
|||
await root.preboot();
|
||||
await root.setup();
|
||||
await expect(root.start()).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715272 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]`
|
||||
`[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715248 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]`
|
||||
);
|
||||
|
||||
await retryAsync(
|
||||
|
@ -131,7 +131,7 @@ describe('migration v2', () => {
|
|||
expect(
|
||||
records.find((rec) =>
|
||||
rec.message.startsWith(
|
||||
`Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715272 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`
|
||||
`Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715248 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`
|
||||
)
|
||||
)
|
||||
).toBeDefined();
|
||||
|
|
|
@ -126,7 +126,7 @@ describe('migration v2 with corrupt saved object documents', () => {
|
|||
},
|
||||
{
|
||||
mode: 'contain',
|
||||
value: 'at tryTransformDoc',
|
||||
value: 'at transform',
|
||||
},
|
||||
{
|
||||
mode: 'equal',
|
||||
|
@ -146,7 +146,7 @@ describe('migration v2 with corrupt saved object documents', () => {
|
|||
},
|
||||
{
|
||||
mode: 'contain',
|
||||
value: 'at tryTransformDoc',
|
||||
value: 'at transform',
|
||||
},
|
||||
{
|
||||
mode: 'equal',
|
||||
|
|
|
@ -196,7 +196,7 @@ describe('migration v2', () => {
|
|||
migratedDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`foo:${i}`);
|
||||
expect(doc.foo.status).toBe(`migrated`);
|
||||
expect(doc.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -215,7 +215,7 @@ describe('migration v2', () => {
|
|||
migratedDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`foo:${i}`);
|
||||
expect(doc.foo.status).toBe(`migrated`);
|
||||
expect(doc.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -234,7 +234,7 @@ describe('migration v2', () => {
|
|||
migratedDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`foo:${i}`);
|
||||
expect(doc.foo.status).toBe(`migrated`);
|
||||
expect(doc.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -253,7 +253,7 @@ describe('migration v2', () => {
|
|||
migratedDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`foo:${i}`);
|
||||
expect(doc.foo.status).toBe(`migrated`);
|
||||
expect(doc.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -94,8 +94,8 @@ describe('migration v2', () => {
|
|||
|
||||
expect(migratedDocs.length).toBe(1);
|
||||
const [doc] = migratedDocs;
|
||||
expect(doc._source.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc._source.coreMigrationVersion).toBe('8.0.0');
|
||||
expect(doc._source.coreMigrationVersion).toBe('8.8.0');
|
||||
expect(doc._source.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ describe('migrating from 7.3.0-xpack which used v1 migrations', () => {
|
|||
const migrationsMap = typeof migrations === 'function' ? migrations() : migrations;
|
||||
const migrationsKeys = migrationsMap ? Object.keys(migrationsMap) : [];
|
||||
if (convertToMultiNamespaceTypeVersion) {
|
||||
// Setting this option registers a conversion migration that is reflected in the object's `migrationVersions` field
|
||||
// Setting this option registers a conversion migration that is reflected in the object's `typeMigrationVersions` field
|
||||
migrationsKeys.push(convertToMultiNamespaceTypeVersion);
|
||||
}
|
||||
const highestVersion = migrationsKeys.sort(Semver.compare).reverse()[0];
|
||||
|
@ -150,9 +150,8 @@ describe('migrating from 7.3.0-xpack which used v1 migrations', () => {
|
|||
doc: SavedObjectsRawDoc,
|
||||
expectedVersions: Record<string, string | undefined>
|
||||
) => {
|
||||
const migrationVersions = doc._source.migrationVersion;
|
||||
const type = doc._source.type;
|
||||
expect(migrationVersions ? migrationVersions[type] : undefined).toEqual(expectedVersions[type]);
|
||||
expect(doc._source.typeMigrationVersion).toEqual(expectedVersions[type]);
|
||||
};
|
||||
|
||||
const stopServers = async () => {
|
||||
|
|
|
@ -191,7 +191,7 @@ describe('migration v2', () => {
|
|||
migratedFooDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`foo:${i}`);
|
||||
expect(doc.foo.status).toBe(`migrated_${i}`);
|
||||
expect(doc.migrationVersion.foo).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
|
||||
const migratedBarDocs = await fetchDocs(esClient, migratedIndexAlias, 'bar');
|
||||
|
@ -199,7 +199,7 @@ describe('migration v2', () => {
|
|||
migratedBarDocs.forEach((doc, i) => {
|
||||
expect(doc.id).toBe(`bar:${i}`);
|
||||
expect(doc.bar.status).toBe(`migrated_${i}`);
|
||||
expect(doc.migrationVersion.bar).toBe('7.14.0');
|
||||
expect(doc.typeMigrationVersion).toBe('7.14.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -187,8 +187,8 @@ describe('migration v2', () => {
|
|||
foo: { name: 'Foo 1 default' },
|
||||
references: [],
|
||||
namespaces: ['default'],
|
||||
migrationVersion: { foo: '8.0.0' },
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.0.0',
|
||||
},
|
||||
{
|
||||
id: `foo:${newFooId}`,
|
||||
|
@ -197,8 +197,8 @@ describe('migration v2', () => {
|
|||
references: [],
|
||||
namespaces: ['spacex'],
|
||||
originId: '1',
|
||||
migrationVersion: { foo: '8.0.0' },
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.0.0',
|
||||
},
|
||||
{
|
||||
// new object for spacex:foo:1
|
||||
|
@ -211,9 +211,9 @@ describe('migration v2', () => {
|
|||
targetType: 'foo',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { 'legacy-url-alias': '8.2.0' },
|
||||
references: [],
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.2.0',
|
||||
},
|
||||
{
|
||||
id: 'bar:1',
|
||||
|
@ -221,8 +221,8 @@ describe('migration v2', () => {
|
|||
bar: { nomnom: 1 },
|
||||
references: [{ type: 'foo', id: '1', name: 'Foo 1 default' }],
|
||||
namespaces: ['default'],
|
||||
migrationVersion: { bar: '8.0.0' },
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.0.0',
|
||||
},
|
||||
{
|
||||
id: `bar:${newBarId}`,
|
||||
|
@ -231,8 +231,8 @@ describe('migration v2', () => {
|
|||
references: [{ type: 'foo', id: newFooId, name: 'Foo 1 spacex' }],
|
||||
namespaces: ['spacex'],
|
||||
originId: '1',
|
||||
migrationVersion: { bar: '8.0.0' },
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.0.0',
|
||||
},
|
||||
{
|
||||
// new object for spacex:bar:1
|
||||
|
@ -245,9 +245,9 @@ describe('migration v2', () => {
|
|||
targetType: 'bar',
|
||||
purpose: 'savedObjectConversion',
|
||||
},
|
||||
migrationVersion: { 'legacy-url-alias': '8.2.0' },
|
||||
references: [],
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: expect.any(String),
|
||||
typeMigrationVersion: '8.2.0',
|
||||
},
|
||||
].sort(sortByTypeAndId)
|
||||
);
|
||||
|
|
|
@ -147,7 +147,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[expect.objectContaining({ migrationVersion: {} })],
|
||||
[expect.objectContaining({ typeMigrationVersion: '' })],
|
||||
expect.any(Object) // options
|
||||
);
|
||||
});
|
||||
|
|
|
@ -161,7 +161,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[expect.objectContaining({ migrationVersion: {} })],
|
||||
[expect.objectContaining({ typeMigrationVersion: '' })],
|
||||
expect.any(Object) // options
|
||||
);
|
||||
});
|
||||
|
@ -199,7 +199,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[{ type, id, attributes, migrationVersion: {} }],
|
||||
[{ type, id, attributes, typeMigrationVersion: '' }],
|
||||
expect.objectContaining({ overwrite: undefined })
|
||||
);
|
||||
});
|
||||
|
@ -238,7 +238,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[{ type, id, attributes, migrationVersion: {} }],
|
||||
[{ type, id, attributes, typeMigrationVersion: '' }],
|
||||
expect.objectContaining({ overwrite: true })
|
||||
);
|
||||
});
|
||||
|
@ -282,7 +282,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[{ type, id, attributes, references, migrationVersion: {} }],
|
||||
[{ type, id, attributes, references, typeMigrationVersion: '' }],
|
||||
expect.objectContaining({ overwrite: undefined })
|
||||
);
|
||||
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1);
|
||||
|
@ -331,7 +331,7 @@ describe(`POST ${URL}`, () => {
|
|||
});
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); // successResults objects were created because no resolvable errors are present
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[{ type, id, attributes, references, migrationVersion: {} }],
|
||||
[{ type, id, attributes, references, typeMigrationVersion: '' }],
|
||||
expect.objectContaining({ overwrite: undefined })
|
||||
);
|
||||
expect(savedObjectsClient.bulkGet).not.toHaveBeenCalled();
|
||||
|
|
|
@ -25,11 +25,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
name: 'Kibana Sample Data eCommerce',
|
||||
typeMeta: '{}',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
migrationVersion: {
|
||||
'index-pattern': '7.11.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.11.0',
|
||||
references: [],
|
||||
type: 'index-pattern',
|
||||
updated_at: '2021-08-05T12:23:57.577Z',
|
||||
|
@ -49,11 +47,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
visState:
|
||||
'{"title":"[eCommerce] Promotion Tracking","type":"metrics","aggs":[],"params":{"time_range_mode":"entire_time_range","id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"ea20ae70-b88d-11e8-a451-f37365e9f268","color":"rgba(211,96,134,1)","split_mode":"everything","metrics":[{"id":"ea20ae71-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":"2","point_size":"5","fill":"0","stacked":"none","filter":{"query":"products.product_name:*trouser*","language":"lucene"},"label":"Revenue Trousers","value_template":"${{value}}","split_color_mode":"gradient"},{"id":"062d77b0-b88e-11e8-a451-f37365e9f268","color":"rgba(84,179,153,1)","split_mode":"everything","metrics":[{"id":"062d77b1-b88e-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":"2","point_size":"05","fill":"0","stacked":"none","filter":{"query":"products.product_name:*watch*","language":"lucene"},"label":"Revenue Watches","value_template":"${{value}}","split_color_mode":"gradient"},{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(96,146,192,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":"2","point_size":"5","fill":"0","stacked":"none","filter":{"query":"products.product_name:*bag*","language":"lucene"},"label":"Revenue Bags","value_template":"${{value}}","split_color_mode":"gradient"},{"id":"faa2c170-b88d-11e8-a451-f37365e9f268","color":"rgba(202,142,174,1)","split_mode":"everything","metrics":[{"id":"faa2c171-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":"2","point_size":"5","fill":"0","stacked":"none","filter":{"query":"products.product_name:*cocktail dress*","language":"lucene"},"label":"Revenue Cocktail Dresses","value_template":"${{value}}","split_color_mode":"gradient"}],"time_field":"order_date","interval":"12h","use_kibana_indexes":true,"axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"legend_position":"bottom","annotations":[{"fields":"taxful_total_price","template":"Ring the bell! ${{taxful_total_price}}","query_string":{"query":"taxful_total_price:>250","language":"lucene"},"id":"c8c30be0-b88f-11e8-a451-f37365e9f268","color":"rgba(25,77,51,1)","time_field":"order_date","icon":"fa-bell","ignore_global_filters":1,"ignore_panel_filters":1,"index_pattern_ref_name":"metrics_1_index_pattern"}],"tooltip_mode":"show_all","drop_last_bucket":0,"isModelInvalid":false,"index_pattern_ref_name":"metrics_0_index_pattern"}}',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: '45e07720-b890-11e8-a6d9-e546fe2bba5f',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -84,11 +80,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
visState:
|
||||
'{"title":"[eCommerce] Sold Products per Day","type":"metrics","aggs":[],"params":{"time_range_mode":"entire_time_range","id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"gauge","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"#68BC00","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count"},{"id":"fd1e1b90-e4e3-11eb-8234-cb7bfd534fce","type":"math","variables":[{"id":"00374270-e4e4-11eb-8234-cb7bfd534fce","name":"c","field":"61ca57f2-469d-11e7-af02-69e470af7417"}],"script":"params.c / (params._interval / 1000 / 60 / 60 / 24)"}],"separate_axis":0,"axis_position":"right","formatter":"0.0","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Trxns / day","split_color_mode":"gradient","value_template":""}],"time_field":"order_date","interval":"1d","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"gauge_color_rules":[{"value":150,"id":"6da070c0-b891-11e8-b645-195edeb9de84","gauge":"rgba(104,188,0,1)","operator":"gte"},{"value":150,"id":"9b0cdbc0-b891-11e8-b645-195edeb9de84","gauge":"rgba(244,78,59,1)","operator":"lt"}],"gauge_width":"15","gauge_inner_width":"10","gauge_style":"half","filter":"","gauge_max":"300","use_kibana_indexes":true,"hide_last_value_indicator":true,"tooltip_mode":"show_all","drop_last_bucket":0,"isModelInvalid":false,"index_pattern_ref_name":"metrics_0_index_pattern"}}',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'b80e6540-b891-11e8-a6d9-e546fe2bba5f',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -122,11 +116,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
}),
|
||||
version: 1,
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: '3ba638e0-b894-11e8-a6d9-e546fe2bba5f',
|
||||
migrationVersion: {
|
||||
search: '7.9.3',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.9.3',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -143,8 +135,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-10-28T15:07:24.077Z',
|
||||
version: '1',
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.0.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.ecommerceSpec.salesCountMapTitle', {
|
||||
defaultMessage: '[eCommerce] Sales Count Map',
|
||||
|
@ -179,11 +171,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
visState:
|
||||
'{"title":"[eCommerce] Markdown","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":false,"markdown":"## Sample eCommerce Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"aggs":[]}',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'c00d1f90-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [],
|
||||
type: 'visualization',
|
||||
updated_at: '2021-08-05T12:43:35.817Z',
|
||||
|
@ -328,11 +318,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: '% of target revenue ($10k)',
|
||||
visualizationType: 'lnsXY',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'c762b7a0-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -386,11 +374,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Sum of revenue',
|
||||
visualizationType: 'lnsMetric',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'ce02e260-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -444,11 +430,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Median spending',
|
||||
visualizationType: 'lnsMetric',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'd5f90030-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -600,11 +584,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Transactions per day',
|
||||
visualizationType: 'lnsXY',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'dde978b0-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -666,11 +648,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Avg. items sold',
|
||||
visualizationType: 'lnsMetric',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'e3902840-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -793,11 +773,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Breakdown by category',
|
||||
visualizationType: 'lnsXY',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'eddf7850-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -993,11 +971,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Daily comparison',
|
||||
visualizationType: 'lnsDatatable',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: 'ff6a21b0-f5ea-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -1109,11 +1085,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Top products this week',
|
||||
visualizationType: 'lnsXY',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: '03071e90-f5eb-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -1225,11 +1199,9 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
title: 'Top products last week',
|
||||
visualizationType: 'lnsXY',
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
id: '06379e00-f5eb-11eb-a78e-83aac3c38a60',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
|
@ -1370,9 +1342,7 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
},
|
||||
],
|
||||
migrationVersion: {
|
||||
dashboard: '8.5.0',
|
||||
},
|
||||
coreMigrationVersion: '8.6.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.5.0',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -17,9 +17,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'search',
|
||||
updated_at: '2021-07-01T20:41:40.379Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
search: '7.9.3',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.9.3',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.flightLogTitle', {
|
||||
defaultMessage: '[Flights] Flight Log',
|
||||
|
@ -57,9 +56,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2018-05-09T15:49:03.736Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.delaysAndCancellationsTitle', {
|
||||
defaultMessage: '[Flights] Delays & Cancellations',
|
||||
|
@ -91,9 +89,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2018-05-09T15:49:03.736Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.delayBucketsTitle', {
|
||||
defaultMessage: '[Flights] Delay Buckets',
|
||||
|
@ -126,7 +123,6 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-07-07T01:48:55.366Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.destinationWeatherTitle', {
|
||||
defaultMessage: '[Flights] Destination Weather',
|
||||
|
@ -154,7 +150,6 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-07-07T01:36:42.568Z',
|
||||
version: '4',
|
||||
migrationVersion: {},
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.airportConnectionsTitle', {
|
||||
defaultMessage: '[Flights] Airport Connections (Hover Over Airport)',
|
||||
|
@ -175,9 +170,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2018-05-09T15:49:03.736Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.flightsSpec.departuresCountMapTitle', {
|
||||
defaultMessage: '[Flights] Departures Count Map',
|
||||
|
@ -205,7 +199,6 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'index-pattern',
|
||||
updated_at: '2018-05-09T15:49:03.736Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
attributes: {
|
||||
title: 'kibana_sample_data_flights',
|
||||
name: 'Kibana Sample Data Flights',
|
||||
|
@ -408,9 +401,7 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
|
||||
},
|
||||
],
|
||||
migrationVersion: {
|
||||
dashboard: '8.5.0',
|
||||
},
|
||||
coreMigrationVersion: '8.6.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.5.0',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -16,8 +16,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-10-28T15:07:36.622Z',
|
||||
version: '1',
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.0.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.visitorsMapTitle', {
|
||||
defaultMessage: '[Logs] Visitors Map',
|
||||
|
@ -45,9 +45,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-07-21T21:33:42.541Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.heatmapTitle', {
|
||||
defaultMessage: '[Logs] Unique Destination Heatmap',
|
||||
|
@ -68,9 +67,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-07-21T18:52:13.586Z',
|
||||
version: '2',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.hostVisitsBytesTableTitle', {
|
||||
defaultMessage: '[Logs] Host, Visits and Bytes Table',
|
||||
|
@ -97,8 +95,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-10-28T14:38:21.435Z',
|
||||
version: '2',
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.0.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.goalsTitle', {
|
||||
defaultMessage: '[Logs] Goals',
|
||||
|
@ -127,7 +125,6 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2018-08-29T13:22:17.617Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle', {
|
||||
defaultMessage: '[Logs] Machine OS and Destination Sankey Chart',
|
||||
|
@ -148,9 +145,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'visualization',
|
||||
updated_at: '2021-07-21T18:52:13.586Z',
|
||||
version: '2',
|
||||
migrationVersion: {
|
||||
visualization: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.responseCodesOverTimeTitle', {
|
||||
defaultMessage: '[Logs] Response Codes Over Time + Annotations',
|
||||
|
@ -182,9 +178,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'lens',
|
||||
updated_at: '2021-07-21T22:14:59.793Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
lens: '7.14.0',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.14.0',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.bytesDistributionTitle', {
|
||||
defaultMessage: '[Logs] Bytes distribution',
|
||||
|
@ -367,7 +362,6 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
type: 'index-pattern',
|
||||
updated_at: '2018-08-29T13:22:17.617Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
attributes: {
|
||||
title: 'kibana_sample_data_logs',
|
||||
name: 'Kibana Sample Data Logs',
|
||||
|
@ -508,19 +502,16 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
},
|
||||
],
|
||||
migrationVersion: {
|
||||
dashboard: '8.5.0',
|
||||
},
|
||||
coreMigrationVersion: '8.6.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '8.5.0',
|
||||
},
|
||||
{
|
||||
id: '2f360f30-ea74-11eb-b4c6-3d2afc1cb389',
|
||||
type: 'search',
|
||||
updated_at: '2021-07-21T22:37:09.415Z',
|
||||
version: '1',
|
||||
migrationVersion: {
|
||||
search: '7.9.3',
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: '7.9.3',
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.discoverTitle', {
|
||||
defaultMessage: '[Logs] Visits',
|
||||
|
|
|
@ -70,10 +70,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
attributes: {
|
||||
title: 'A great new dashboard',
|
||||
},
|
||||
migrationVersion: {
|
||||
dashboard: resp.body.saved_objects[1].migrationVersion.dashboard,
|
||||
},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.saved_objects[1].typeMigrationVersion,
|
||||
references: [],
|
||||
namespaces: [SPACE_ID],
|
||||
},
|
||||
|
|
|
@ -71,8 +71,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
kibanaSavedObjectMeta:
|
||||
resp.body.saved_objects[0].attributes.kibanaSavedObjectMeta,
|
||||
},
|
||||
migrationVersion: resp.body.saved_objects[0].migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.saved_objects[0].typeMigrationVersion,
|
||||
namespaces: ['default'],
|
||||
references: [
|
||||
{
|
||||
|
@ -102,13 +102,13 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
defaultIndex: '91200a00-9efd-11e7-acb3-3dab96693fab',
|
||||
},
|
||||
namespaces: ['default'],
|
||||
migrationVersion: resp.body.saved_objects[2].migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.saved_objects[2].typeMigrationVersion,
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(resp.body.saved_objects[0].migrationVersion).to.be.ok();
|
||||
expect(resp.body.saved_objects[0].typeMigrationVersion).to.be.ok();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(resp.body).to.eql({
|
||||
id: resp.body.id,
|
||||
type: 'visualization',
|
||||
migrationVersion: resp.body.migrationVersion,
|
||||
coreMigrationVersion: '8.0.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.typeMigrationVersion,
|
||||
updated_at: resp.body.updated_at,
|
||||
created_at: resp.body.created_at,
|
||||
version: resp.body.version,
|
||||
|
@ -60,7 +60,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
references: [],
|
||||
namespaces: ['default'],
|
||||
});
|
||||
expect(resp.body.migrationVersion).to.be.ok();
|
||||
expect(resp.body.typeMigrationVersion).to.be.ok();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -71,11 +71,11 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
attributes: {
|
||||
title: 'My favorite vis',
|
||||
},
|
||||
coreMigrationVersion: '1.2.3',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
})
|
||||
.expect(200)
|
||||
.then((resp) => {
|
||||
expect(resp.body.coreMigrationVersion).to.eql('1.2.3');
|
||||
expect(resp.body.coreMigrationVersion).to.eql('8.8.0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -348,8 +348,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 1,
|
||||
},
|
||||
id: 'be3733a0-9efe-11e7-acb3-3dab96693fab',
|
||||
migrationVersion: objects[0].migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: objects[0].typeMigrationVersion,
|
||||
references: [
|
||||
{
|
||||
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
|
@ -362,7 +362,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
created_at: objects[0].created_at,
|
||||
version: objects[0].version,
|
||||
});
|
||||
expect(objects[0].migrationVersion).to.be.ok();
|
||||
expect(objects[0].typeMigrationVersion).to.be.ok();
|
||||
expect(() =>
|
||||
JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)
|
||||
).not.to.throwError();
|
||||
|
@ -409,8 +409,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 1,
|
||||
},
|
||||
id: 'be3733a0-9efe-11e7-acb3-3dab96693fab',
|
||||
migrationVersion: objects[0].migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: objects[0].typeMigrationVersion,
|
||||
references: [
|
||||
{
|
||||
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
|
@ -423,7 +423,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
created_at: objects[0].created_at,
|
||||
version: objects[0].version,
|
||||
});
|
||||
expect(objects[0].migrationVersion).to.be.ok();
|
||||
expect(objects[0].typeMigrationVersion).to.be.ok();
|
||||
expect(() =>
|
||||
JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)
|
||||
).not.to.throwError();
|
||||
|
@ -475,8 +475,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 1,
|
||||
},
|
||||
id: 'be3733a0-9efe-11e7-acb3-3dab96693fab',
|
||||
migrationVersion: objects[0].migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: objects[0].typeMigrationVersion,
|
||||
references: [
|
||||
{
|
||||
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
|
@ -489,7 +489,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
created_at: objects[0].updated_at,
|
||||
version: objects[0].version,
|
||||
});
|
||||
expect(objects[0].migrationVersion).to.be.ok();
|
||||
expect(objects[0].typeMigrationVersion).to.be.ok();
|
||||
expect(() =>
|
||||
JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)
|
||||
).not.to.throwError();
|
||||
|
|
|
@ -48,7 +48,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(resp.body.saved_objects.map((so: { id: string }) => so.id)).to.eql([
|
||||
'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
]);
|
||||
expect(resp.body.saved_objects[0].migrationVersion).to.be.ok();
|
||||
expect(resp.body.saved_objects[0].typeMigrationVersion).to.be.ok();
|
||||
}));
|
||||
|
||||
describe('unknown type', () => {
|
||||
|
@ -124,7 +124,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
namespaces: so.namespaces,
|
||||
}))
|
||||
).to.eql([{ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', namespaces: [SPACE_ID] }]);
|
||||
expect(resp.body.saved_objects[0].migrationVersion).to.be.ok();
|
||||
expect(resp.body.saved_objects[0].typeMigrationVersion).to.be.ok();
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
updated_at: resp.body.updated_at,
|
||||
created_at: resp.body.created_at,
|
||||
version: resp.body.version,
|
||||
migrationVersion: resp.body.migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.typeMigrationVersion,
|
||||
attributes: {
|
||||
title: 'Count of requests',
|
||||
description: '',
|
||||
|
@ -56,7 +56,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
],
|
||||
namespaces: ['default'],
|
||||
});
|
||||
expect(resp.body.migrationVersion).to.be.ok();
|
||||
expect(resp.body.typeMigrationVersion).to.be.ok();
|
||||
}));
|
||||
|
||||
describe('doc does not exist', () => {
|
||||
|
|
|
@ -41,8 +41,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
updated_at: '2015-01-01T00:00:00.000Z',
|
||||
created_at: '2015-01-01T00:00:00.000Z',
|
||||
version: resp.body.saved_object.version,
|
||||
migrationVersion: resp.body.saved_object.migrationVersion,
|
||||
coreMigrationVersion: '7.14.0',
|
||||
coreMigrationVersion: '8.8.0',
|
||||
typeMigrationVersion: resp.body.saved_object.typeMigrationVersion,
|
||||
attributes: {
|
||||
title: 'Count of requests',
|
||||
description: '',
|
||||
|
@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
outcome: 'exactMatch',
|
||||
});
|
||||
expect(resp.body.saved_object.migrationVersion).to.be.ok();
|
||||
expect(resp.body.saved_object.typeMigrationVersion).to.be.ok();
|
||||
}));
|
||||
|
||||
describe('doc does not exist', () => {
|
||||
|
|
|
@ -754,11 +754,11 @@ export class DataRecognizer {
|
|||
.map((o) => o.savedObject!);
|
||||
if (filteredSavedObjects.length) {
|
||||
results = await this._savedObjectsClient.bulkCreate(
|
||||
// Add an empty migrationVersion attribute to each saved object to ensure
|
||||
// Add an empty typeMigrationVersion attribute to each saved object to ensure
|
||||
// it is automatically migrated to the 7.0+ format with a references attribute.
|
||||
filteredSavedObjects.map((doc) => ({
|
||||
...doc,
|
||||
migrationVersion: {},
|
||||
typeMigrationVersion: '',
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function ({ getService }) {
|
|||
type: 'index-pattern',
|
||||
},
|
||||
]);
|
||||
expect(resp.body.migrationVersion).to.eql({ map: '8.4.0' }); // migrtionVersion is derived from both "migrations" and "convertToMultiNamespaceVersion" fields when the object is registered
|
||||
expect(resp.body.typeMigrationVersion).to.be('8.4.0'); // typeMigrationVersion is derived from both "migrations" and "convertToMultiNamespaceVersion" fields when the object is registered
|
||||
expect(resp.body.attributes.layerListJSON.includes('indexPatternRefName')).to.be(true);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue