Fix issue with duplicate references in error object when copying saved objects to space (#200053)

Closes https://github.com/elastic/kibana/issues/158027

## Summary

Simply dedupes references to objects if they are part of the
missing_references in the copy saved objects to SO endpoint

### Notes
- Update forEach over SOs to a regular for loop since we had a couple of
early exit scenarios
- Checks against the set for references already added to the missing
list and adds only if not present

------

**Old response: Note the duplicate references**

<img width="400" alt="Screenshot 2024-11-14 at 01 52 54"
src="https://github.com/user-attachments/assets/67078080-e39d-43b2-bf7c-7abb76866fa4">


**New response**

<img width="800" alt="Screenshot 2024-11-14 at 01 50 41"
src="https://github.com/user-attachments/assets/776db189-af8c-4522-bb03-f8efbb7cdcd9">


### Release note
Dedupe results from copy saved objects to spaces API when object
contains references to other objects.

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Sid 2024-11-18 16:46:07 +01:00 committed by GitHub
parent 3c3f2c11a9
commit 262b48f1cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 16 deletions

View file

@ -267,4 +267,36 @@ describe('validateReferences()', () => {
'Error fetching references for imported objects'
);
});
// test that when references are missing returns only deduplicated errors
test('returns only deduplicated errors when references are missing', async () => {
const params = setup({
objects: [
{
id: '2',
type: 'visualization',
attributes: { title: 'My Visualization 2' },
references: [
{ name: 'ref_0', type: 'index-pattern', id: '3' },
{ name: 'ref_0', type: 'index-pattern', id: '3' },
],
},
],
});
params.savedObjectsClient.bulkGet.mockResolvedValue({
saved_objects: [createNotFoundError({ type: 'index-pattern', id: '3' })],
});
const result = await validateReferences(params);
expect(result).toEqual([
expect.objectContaining({
type: 'visualization',
id: '2',
error: {
type: 'missing_references',
references: [{ type: 'index-pattern', id: '3' }],
},
}),
]);
});
});

View file

@ -102,30 +102,35 @@ export async function validateReferences(params: ValidateReferencesParams) {
const nonExistingReferenceKeys = await getNonExistingReferenceAsKeys(params);
// Filter out objects with missing references, add to error object
objects.forEach(({ type, id, references, attributes }) => {
if (objectsToSkip.has(`${type}:${id}`)) {
for (const obj of objects) {
const { type, id, references, attributes } = obj;
const objectKey = `${type}:${id}`;
if (objectsToSkip.has(objectKey)) {
// skip objects with retries that have specified `ignoreMissingReferences`
return;
continue;
}
const missingReferences = [];
const enforcedTypeReferences = (references || []).filter(filterReferencesToValidate);
const missingReferences: Array<{ type: string; id: string }> = [];
const enforcedTypeReferences = references?.filter(filterReferencesToValidate) || [];
const seenReferences = new Set();
for (const { type: refType, id: refId } of enforcedTypeReferences) {
if (nonExistingReferenceKeys.includes(`${refType}:${refId}`)) {
const refKey = `${refType}:${refId}`;
if (nonExistingReferenceKeys.includes(refKey) && !seenReferences.has(refKey)) {
missingReferences.push({ type: refType, id: refId });
seenReferences.add(refKey);
}
}
if (missingReferences.length === 0) {
return;
if (missingReferences.length > 0) {
errorMap[objectKey] = {
id,
type,
meta: { title: attributes.title },
error: { type: 'missing_references', references: missingReferences },
};
}
const { title } = attributes;
errorMap[`${type}:${id}`] = {
id,
type,
meta: { title },
error: { type: 'missing_references', references: missingReferences },
};
});
}
return Object.values(errorMap);
}