From 93e521991712bbb3c678c4a706a0dae732cfd555 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Tue, 5 Dec 2023 18:43:49 +0100 Subject: [PATCH] Handle aborted get requests and null domain objects when using ObjectAPI (#7276) * handle null domain objects * add some test coverage for aborting search results * to make test independent --- e2e/tests/functional/search.e2e.spec.js | 26 +++++++++++++++++++++++ src/api/objects/ObjectAPI.js | 7 ++++++ src/plugins/objectMigration/Migrations.js | 6 +++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/e2e/tests/functional/search.e2e.spec.js b/e2e/tests/functional/search.e2e.spec.js index 34b47d791e..449fc41202 100644 --- a/e2e/tests/functional/search.e2e.spec.js +++ b/e2e/tests/functional/search.e2e.spec.js @@ -198,6 +198,32 @@ test.describe('Grand Search', () => { await expect(searchResultDropDown).toContainText('Clock A'); }); + test('Slowly typing after search debounce will abort requests @couchdb', async ({ page }) => { + let requestWasAborted = false; + await createObjectsForSearch(page); + page.on('requestfailed', (request) => { + // check if the request was aborted + if (request.failure().errorText === 'net::ERR_ABORTED') { + requestWasAborted = true; + } + }); + + // Intercept and delay request + const delayInMs = 100; + + await page.route('**', async (route, request) => { + await new Promise((resolve) => setTimeout(resolve, delayInMs)); + route.continue(); + }); + + // Slowly type after search delay + const searchInput = page.getByRole('searchbox', { name: 'Search Input' }); + await searchInput.pressSequentially('Clock', { delay: 200 }); + await expect(page.getByText('Clock B').first()).toBeVisible(); + + expect(requestWasAborted).toBe(true); + }); + test('Validate multiple objects in search results return partial matches', async ({ page }) => { test.info().annotations.push({ type: 'issue', diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index e7a5bd3e55..74aa80d806 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -232,6 +232,10 @@ export default class ObjectAPI { .get(identifier, abortSignal) .then((domainObject) => { delete this.cache[keystring]; + if (!domainObject && abortSignal.aborted) { + // we've aborted the request + return; + } domainObject = this.applyGetInterceptors(identifier, domainObject); if (this.supportsMutation(identifier)) { @@ -791,6 +795,9 @@ export default class ObjectAPI { */ async getOriginalPath(identifier, path = [], abortSignal = null) { const domainObject = await this.get(identifier, abortSignal); + if (!domainObject) { + return []; + } path.push(domainObject); const { location } = domainObject; if (location && !this.#pathContainsDomainObject(location, path)) { diff --git a/src/plugins/objectMigration/Migrations.js b/src/plugins/objectMigration/Migrations.js index 63e43d6ebb..9856b7788a 100644 --- a/src/plugins/objectMigration/Migrations.js +++ b/src/plugins/objectMigration/Migrations.js @@ -180,7 +180,7 @@ define(['uuid'], function ({ v4: uuid }) { { check(domainObject) { return ( - domainObject.type === 'layout' && + domainObject?.type === 'layout' && domainObject.configuration && domainObject.configuration.layout ); @@ -201,7 +201,7 @@ define(['uuid'], function ({ v4: uuid }) { { check(domainObject) { return ( - domainObject.type === 'telemetry.fixed' && + domainObject?.type === 'telemetry.fixed' && domainObject.configuration && domainObject.configuration['fixed-display'] ); @@ -246,7 +246,7 @@ define(['uuid'], function ({ v4: uuid }) { { check(domainObject) { return ( - domainObject.type === 'table' && + domainObject?.type === 'table' && domainObject.configuration && domainObject.configuration.table );