Fix SO export sorting algorithm (#142078)

* Fix SO export sorting algorithm

* improve var name

* adapt another unit  test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pierre Gayvallet 2022-09-29 19:41:06 +02:00 committed by GitHub
parent 5d31e88c5d
commit 4cdd74dfcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 53 additions and 11 deletions

View file

@ -146,16 +146,18 @@ describe('getSortedObjectsForExport()', () => {
attributes = {},
sort = [],
type = 'index-pattern',
idPrefix = '',
}: {
attributes?: Record<string, unknown>;
sort?: string[];
type?: string;
idPrefix?: string;
} = {}
) {
const hits = [];
for (let i = 1; i <= hitCount; i++) {
hits.push({
id: `${i}`,
id: `${idPrefix}${i}`,
type,
attributes,
sort,
@ -247,7 +249,7 @@ describe('getSortedObjectsForExport()', () => {
describe('>1k hits', () => {
const firstMockHits = generateHits(1000, { sort: ['a', 'b'] });
const secondMockHits = generateHits(500);
const secondMockHits = generateHits(500, { idPrefix: 'second-hit-' });
test('requests multiple pages', async () => {
savedObjectsClient.find.mockResolvedValueOnce({

View file

@ -6,7 +6,9 @@
* Side Public License, v 1.
*/
import { range } from 'lodash';
import { sortObjects } from './sort_objects';
import type { SavedObject } from '@kbn/core-saved-objects-common';
describe('sortObjects()', () => {
test('should return on empty array', () => {
@ -309,6 +311,7 @@ describe('sortObjects()', () => {
]
`);
});
test('should not fail on complex circular dependencies', () => {
const docs = [
{
@ -424,4 +427,38 @@ describe('sortObjects()', () => {
]
`);
});
test('should not fail on large graph of objects', () => {
// create an object that references all objects with a higher `index` up to `depth`.
const createComplexNode = (index: number, depth: number): SavedObject => {
return {
type: 'test',
id: `${index}`,
attributes: {},
references: range(index + 1, depth).map((refIndex) => ({
type: 'test',
id: `${refIndex}`,
name: `test-${refIndex}`,
})),
};
};
const createComplexGraph = (depth: number): SavedObject[] => {
const nodes: SavedObject[] = [];
for (let i = 0; i < depth; i++) {
nodes.push(createComplexNode(i, depth));
}
return nodes;
};
const depth = 100;
const graph = createComplexGraph(depth);
const sorted = sortObjects(graph);
expect(sorted.map(({ type, id }) => `${type}:${id}`)).toEqual(
range(depth)
.reverse()
.map((index) => `test:${index}`)
);
});
});

View file

@ -8,27 +8,29 @@
import type { SavedObject } from '@kbn/core-saved-objects-common';
const getId = (object: { type: string; id: string }) => `${object.type}:${object.id}`;
export function sortObjects(savedObjects: SavedObject[]): SavedObject[] {
const path = new Set<SavedObject>();
const traversed = new Set<string>();
const sorted = new Set<SavedObject>();
const objectsByTypeId = new Map(
savedObjects.map((object) => [`${object.type}:${object.id}`, object] as [string, SavedObject])
savedObjects.map((object) => [getId(object), object] as [string, SavedObject])
);
function includeObjects(objects: SavedObject[]) {
for (const object of objects) {
if (path.has(object)) {
const objectId = getId(object);
if (traversed.has(objectId)) {
continue;
}
const refdObjects = object.references
.map((ref) => objectsByTypeId.get(`${ref.type}:${ref.id}`))
const objectRefs = object.references
.map((ref) => objectsByTypeId.get(getId(ref)))
.filter((ref): ref is SavedObject => !!ref);
if (refdObjects.length) {
path.add(object);
includeObjects(refdObjects);
path.delete(object);
traversed.add(objectId);
if (objectRefs.length) {
includeObjects(objectRefs);
}
sorted.add(object);
@ -36,5 +38,6 @@ export function sortObjects(savedObjects: SavedObject[]): SavedObject[] {
}
includeObjects(savedObjects);
return [...sorted];
}