-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Saved object migrations] Collect all documents that fail to transform before stopping the migration #96986
[Saved object migrations] Collect all documents that fail to transform before stopping the migration #96986
Changes from 28 commits
db8feb8
afd4649
96c2901
b0eb13f
0b14388
8714263
1f7a389
d7ba06c
bc949bb
6dd4d89
f798053
2f109a6
e804e74
beee25e
9d7f824
738f5ae
5131c41
9b66353
b3ae792
e87d0e9
736df8c
f697729
c909b3a
d11fd0a
c3081e3
8360a1b
2a6675c
8d16852
2b31763
1765513
9f8e432
0f4c497
9bb9ec1
22a6ca3
c0f0ef6
1814c87
671b286
39f9f28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,8 @@ import { set } from '@elastic/safer-lodash-set'; | |
import _ from 'lodash'; | ||
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; | ||
import { SavedObjectsSerializer } from '../../serialization'; | ||
import { migrateRawDocs } from './migrate_raw_docs'; | ||
import { migrateRawDocs, migrateRawDocsSafely } from './migrate_raw_docs'; | ||
import { TransformSavedObjectDocumentError } from './transform_saved_object_document_error'; | ||
|
||
describe('migrateRawDocs', () => { | ||
test('converts raw docs to saved objects', async () => { | ||
|
@@ -120,3 +121,156 @@ describe('migrateRawDocs', () => { | |
).rejects.toThrowErrorMatchingInlineSnapshot(`"error during transform"`); | ||
}); | ||
}); | ||
|
||
describe('migrateRawDocsSafely', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('converts raw docs to saved objects', async () => { | ||
let result: any; | ||
const transform = jest.fn<any, any>((doc: any) => [ | ||
set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), | ||
]); | ||
const task = migrateRawDocsSafely( | ||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()), | ||
transform, | ||
[ | ||
{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }, | ||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, | ||
] | ||
); | ||
try { | ||
result = await task(); | ||
} catch (e) { | ||
/** ignore */ | ||
} | ||
TinaHeiligers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect(result._tag).toEqual('Right'); | ||
expect(result.right.processedDocs).toEqual([ | ||
{ | ||
_id: 'a:b', | ||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, | ||
}, | ||
{ | ||
_id: 'c:d', | ||
_source: { type: 'c', c: { name: 'HOI!' }, migrationVersion: {}, references: [] }, | ||
}, | ||
]); | ||
|
||
const obj1 = { | ||
id: 'b', | ||
type: 'a', | ||
attributes: { name: 'AAA' }, | ||
migrationVersion: {}, | ||
references: [], | ||
}; | ||
const obj2 = { | ||
id: 'd', | ||
type: 'c', | ||
attributes: { name: 'DDD' }, | ||
migrationVersion: {}, | ||
references: [], | ||
}; | ||
expect(transform).toHaveBeenCalledTimes(2); | ||
expect(transform).toHaveBeenNthCalledWith(1, obj1); | ||
expect(transform).toHaveBeenNthCalledWith(2, obj2); | ||
}); | ||
|
||
test('returns a `left` tag when encountering a corrupt saved object document', async () => { | ||
let result: any; | ||
const transform = jest.fn<any, any>((doc: any) => [ | ||
set(_.cloneDeep(doc), 'attributes.name', 'TADA'), | ||
]); | ||
const task = migrateRawDocsSafely( | ||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()), | ||
transform, | ||
[ | ||
{ _id: 'foo:b', _source: { type: 'a', a: { name: 'AAA' } } }, | ||
{ _id: 'c:d', _source: { type: 'c', c: { name: 'DDD' } } }, | ||
] | ||
); | ||
try { | ||
result = await task(); | ||
} catch (e) { | ||
result = e; | ||
} | ||
expect(transform).toHaveBeenCalledTimes(1); | ||
expect(result._tag).toEqual('Left'); | ||
expect(Object.keys(result.left)).toEqual(['type', 'corruptDocumentIds', 'transformErrors']); | ||
expect(result.left.corruptDocumentIds.length).toEqual(1); | ||
expect(result.left.transformErrors.length).toEqual(0); | ||
}); | ||
|
||
test('handles when one document is transformed into multiple documents', async () => { | ||
let result: any; | ||
const transform = jest.fn<any, any>((doc: any) => [ | ||
set(_.cloneDeep(doc), 'attributes.name', 'HOI!'), | ||
{ id: 'bar', type: 'foo', attributes: { name: 'baz' } }, | ||
]); | ||
const task = migrateRawDocsSafely( | ||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()), | ||
transform, | ||
[{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }] | ||
); | ||
try { | ||
result = await task(); | ||
} catch (err) { | ||
/** ignore */ | ||
} | ||
expect(result._tag).toEqual('Right'); | ||
expect(result.right.processedDocs).toEqual([ | ||
{ | ||
_id: 'a:b', | ||
_source: { type: 'a', a: { name: 'HOI!' }, migrationVersion: {}, references: [] }, | ||
}, | ||
{ | ||
_id: 'foo:bar', | ||
_source: { type: 'foo', foo: { name: 'baz' }, references: [] }, | ||
}, | ||
TinaHeiligers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
]); | ||
|
||
const obj = { | ||
id: 'b', | ||
type: 'a', | ||
attributes: { name: 'AAA' }, | ||
migrationVersion: {}, | ||
references: [], | ||
}; | ||
expect(transform).toHaveBeenCalledTimes(1); | ||
expect(transform).toHaveBeenCalledWith(obj); | ||
}); | ||
|
||
test('rejects when the transform function throws an error', async () => { | ||
TinaHeiligers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let result: any; | ||
const transform = jest.fn<any, any>((doc: any) => { | ||
throw new TransformSavedObjectDocumentError( | ||
`${doc.id}`, | ||
`${doc.type}`, | ||
`${doc.namespace}`, | ||
`${doc.type}1.2.3`, | ||
JSON.stringify(doc), | ||
new Error('error during transform') | ||
); | ||
}); | ||
const task = migrateRawDocsSafely( | ||
new SavedObjectsSerializer(new SavedObjectTypeRegistry()), | ||
transform, | ||
[{ _id: 'a:b', _source: { type: 'a', a: { name: 'AAA' } } }] // this is the raw doc | ||
); | ||
try { | ||
result = await task(); | ||
} catch (err) { | ||
/* ignore */ | ||
} | ||
expect(transform).toHaveBeenCalledTimes(1); | ||
expect(result._tag).toEqual('Left'); | ||
expect(result.left.corruptDocumentIds.length).toEqual(0); | ||
expect(result.left.transformErrors.length).toEqual(1); | ||
expect(result.left.transformErrors[0].err.message).toMatchInlineSnapshot( | ||
` | ||
"Failed to transform document b. Transform: a1.2.3 | ||
Doc: {\\"type\\":\\"a\\",\\"id\\":\\"b\\",\\"attributes\\":{\\"name\\":\\"AAA\\"},\\"references\\":[],\\"migrationVersion\\":{}}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do that in a follow up PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, the original error is included in the transformation error as |
||
` | ||
); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why
?? undefined,
is necessary?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A saved object might be multi-namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we need it:
undefined
will be used whendoc.namespace
isundefined
. Isn't it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You raise a very good point! 🤦♀️ I've fixed it.