Tree item abort (#6757)

* adding abortSignal back to composition load
* suppress AbortError console.errors from couch, delay requests for test to trigger abort
---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Jamie V 2023-07-14 10:49:10 -07:00 committed by GitHub
parent cde8fbbb0d
commit 92329b3d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 9 deletions

View File

@ -192,8 +192,12 @@ test.describe('Persistence operations @couchdb', () => {
]);
//Slow down the test a bit
await expect(page.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible();
await expect(page2.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible();
await expect(
page.getByRole('button', { name: `Expand ${myItemsFolderName} folder` })
).toBeVisible();
await expect(
page2.getByRole('button', { name: `Expand ${myItemsFolderName} folder` })
).toBeVisible();
// Both pages: Click the Create button
await Promise.all([

View File

@ -174,6 +174,42 @@ test.describe('Main Tree', () => {
]);
});
});
test('Opening and closing an item before the request has been fulfilled will abort the request @couchdb', async ({
page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
let requestWasAborted = false;
page.on('requestfailed', (request) => {
// check if the request was aborted
if (request.failure().errorText === 'net::ERR_ABORTED') {
requestWasAborted = true;
}
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Foo'
});
// Intercept and delay request
const delayInMs = 500;
await page.route('**', async (route, request) => {
await new Promise((resolve) => setTimeout(resolve, delayInMs));
route.continue();
});
// Quickly Expand/close the root folder
await page
.getByRole('button', {
name: `Expand ${myItemsFolderName} folder`
})
.dblclick({ delay: 400 });
expect(requestWasAborted).toBe(true);
});
});
/**

View File

@ -242,11 +242,16 @@ export default class ObjectAPI {
return domainObject;
})
.catch((error) => {
console.warn(`Failed to retrieve ${keystring}:`, error);
delete this.cache[keystring];
const result = this.applyGetInterceptors(identifier);
return result;
// suppress abort errors
if (error.name === 'AbortError') {
return;
}
console.warn(`Failed to retrieve ${keystring}:`, error);
return this.applyGetInterceptors(identifier);
});
this.cache[keystring] = objectPromise;

View File

@ -248,10 +248,17 @@ describe('The Object API', () => {
});
it('displays a notification in the event of an error', () => {
mockProvider.get.and.returnValue(Promise.reject());
openmct.notifications.warn = jasmine.createSpy('warn');
mockProvider.get.and.returnValue(
Promise.reject({
name: 'Error',
status: 404,
statusText: 'Not Found'
})
);
return objectAPI.get(mockDomainObject.identifier).catch(() => {
expect(openmct.notifications.error).toHaveBeenCalledWith(
expect(openmct.notifications.warn).toHaveBeenCalledWith(
`Failed to retrieve object ${TEST_NAMESPACE}:${TEST_KEY}`
);
});

View File

@ -223,10 +223,16 @@ class CouchObjectProvider {
return json;
} catch (error) {
// abort errors are expected
if (error.name === 'AbortError') {
return;
}
// Network error, CouchDB unreachable.
if (response === null) {
this.indicator.setIndicatorToState(DISCONNECTED);
console.error(error.message);
throw new Error(`CouchDB Error - No response"`);
} else {
if (body?.model && isNotebookOrAnnotationType(body.model)) {

View File

@ -22,7 +22,12 @@
<template>
<span
:class="[controlClass, { 'c-disclosure-triangle--expanded': value }, { 'is-enabled': enabled }]"
tabindex="0"
role="button"
:aria-label="ariaLabelValue"
:aria-expanded="value ? 'true' : 'false'"
@click="handleClick"
@keydown.enter="handleClick"
></span>
</template>
@ -42,6 +47,18 @@ export default {
controlClass: {
type: String,
default: 'c-disclosure-triangle'
},
domainObject: {
type: Object,
default: () => {}
}
},
computed: {
ariaLabelValue() {
const name = this.domainObject.name ? ` ${this.domainObject.name}` : '';
const type = this.domainObject.type ? ` ${this.domainObject.type}` : '';
return `${this.value ? 'Collapse' : 'Expand'}${name}${type}`;
}
},
methods: {

View File

@ -326,12 +326,13 @@ export default {
},
async openTreeItem(parentItem) {
const parentPath = parentItem.navigationPath;
const abortSignal = this.startItemLoad(parentPath);
this.startItemLoad(parentPath);
// pass in abort signal when functional
const childrenItems = await this.loadAndBuildTreeItemsFor(
parentItem.object.identifier,
parentItem.objectPath
parentItem.objectPath,
abortSignal
);
const parentIndex = this.treeItems.indexOf(parentItem);

View File

@ -44,6 +44,7 @@
<view-control
ref="action"
class="c-tree__item__view-control"
:domain-object="node.object"
:value="isOpen || isLoading"
:enabled="!activeSearch && hasComposition"
@input="itemAction()"