Fix bulkResolve for duplicate requests (#113798) (#113826)

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-10-04 18:08:36 -04:00 committed by GitHub
parent 88ded09bfa
commit 6be9fbe9c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 30 additions and 40 deletions

View file

@ -216,61 +216,51 @@ describe('SavedObjectsClient', () => {
`);
});
test('removes duplicates when calling `_bulk_resolve`', async () => {
test('handles duplicates correctly', async () => {
// Await #resolve call to ensure batchQueue is empty and throttle has reset
mockResolvedObjects({ ...doc, type: 'type2' });
await savedObjectsClient.resolve('type2', doc.id);
http.fetch.mockClear();
mockResolvedObjects(doc, { ...doc, type: 'some-type', id: 'some-id' }); // the client will only request two objects, so we only mock two results
savedObjectsClient.resolve(doc.type, doc.id);
savedObjectsClient.resolve('some-type', 'some-id');
await savedObjectsClient.resolve(doc.type, doc.id);
mockResolvedObjects(doc, { ...doc, type: 'type2' }, { ...doc, type: 'type3' }); // the client will only request three objects, so we only mock three results
const call1 = savedObjectsClient.resolve(doc.type, doc.id);
const call2 = savedObjectsClient.resolve('type2', doc.id);
const call3 = savedObjectsClient.resolve(doc.type, doc.id);
const objFromCall4 = await savedObjectsClient.resolve('type3', doc.id);
const objFromCall1 = await call1;
const objFromCall2 = await call2;
const objFromCall3 = await call3;
// Assertion 1: all calls should return the expected object
expect(objFromCall1.saved_object).toEqual(
expect.objectContaining({ type: doc.type, id: doc.id, error: undefined })
);
expect(objFromCall2.saved_object).toEqual(
expect.objectContaining({ type: 'type2', id: doc.id, error: undefined })
);
expect(objFromCall3.saved_object).toEqual(
expect.objectContaining({ type: doc.type, id: doc.id, error: undefined })
);
expect(objFromCall4.saved_object).toEqual(
expect.objectContaining({ type: 'type3', id: doc.id, error: undefined })
);
// Assertion 2: requests should be deduplicated (call1 and call3)
expect(http.fetch).toHaveBeenCalledTimes(1);
expect(http.fetch.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/saved_objects/_bulk_resolve",
Object {
"body": "[{\\"id\\":\\"AVwSwFxtcMV38qjDZoQg\\",\\"type\\":\\"config\\"},{\\"id\\":\\"some-id\\",\\"type\\":\\"some-type\\"}]",
"body": "[{\\"id\\":\\"AVwSwFxtcMV38qjDZoQg\\",\\"type\\":\\"config\\"},{\\"id\\":\\"AVwSwFxtcMV38qjDZoQg\\",\\"type\\":\\"type2\\"},{\\"id\\":\\"AVwSwFxtcMV38qjDZoQg\\",\\"type\\":\\"type3\\"}]",
"method": "POST",
"query": undefined,
},
]
`);
});
test('resolves with correct object when there are duplicates present', async () => {
// Await #resolve call to ensure batchQueue is empty and throttle has reset
mockResolvedObjects({ ...doc, type: 'type2' });
await savedObjectsClient.resolve('type2', doc.id);
http.fetch.mockClear();
mockResolvedObjects(doc);
const call1 = savedObjectsClient.resolve(doc.type, doc.id);
const objFromCall2 = await savedObjectsClient.resolve(doc.type, doc.id);
const objFromCall1 = await call1;
expect(objFromCall1.saved_object.type).toBe(doc.type);
expect(objFromCall1.saved_object.id).toBe(doc.id);
expect(objFromCall2.saved_object.type).toBe(doc.type);
expect(objFromCall2.saved_object.id).toBe(doc.id);
});
test('do not share instances or references between duplicate callers', async () => {
// Await #resolve call to ensure batchQueue is empty and throttle has reset
await savedObjectsClient.resolve('type2', doc.id);
mockResolvedObjects({ ...doc, type: 'type2' });
http.fetch.mockClear();
mockResolvedObjects(doc);
const call1 = savedObjectsClient.resolve(doc.type, doc.id);
const objFromCall2 = await savedObjectsClient.resolve(doc.type, doc.id);
const objFromCall1 = await call1;
// Assertion 3: deduplicated requests should not share response object instances or references
objFromCall1.saved_object.set('title', 'new title');
expect(objFromCall2.saved_object.get('title')).toEqual('Example title');
expect(objFromCall3.saved_object.get('title')).toEqual('Example title'); // unchanged
});
test('resolves with ResolvedSimpleSavedObject instance', async () => {

View file

@ -168,13 +168,13 @@ const getObjectsToResolve = (queue: BatchResolveQueueEntry[]) => {
const responseIndices: number[] = [];
const objectsToResolve: ObjectTypeAndId[] = [];
const inserted = new Map<string, number>();
queue.forEach(({ id, type }, currentIndex) => {
queue.forEach(({ id, type }) => {
const key = `${type}|${id}`;
const indexForTypeAndId = inserted.get(key);
if (indexForTypeAndId === undefined) {
inserted.set(key, currentIndex);
inserted.set(key, objectsToResolve.length);
responseIndices.push(objectsToResolve.length);
objectsToResolve.push({ id, type });
responseIndices.push(currentIndex);
} else {
responseIndices.push(indexForTypeAndId);
}