Merge remote-tracking branch 'origin/6708-displays-with-a-large-number-of-plots-take-a-long-time-to-load-due-to-annotation-requests' into image-tagging-plus-map-tagging-plus-batching-requests

This commit is contained in:
Mazzella, Jesse D. (ARC-TI)[KBR Wyle Services, LLC] 2023-06-13 11:28:10 -07:00
commit 92cba55f88
2 changed files with 132 additions and 10 deletions

View File

@ -205,6 +205,71 @@ test.describe('Display Layout', () => {
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
page
}) => {
// Create another Sine Wave Generator
const anotherSineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator'
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Test Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
let layoutGridHolder = page.locator('.l-layout__grid-holder');
// eslint-disable-next-line playwright/no-force-option
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true });
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
const anotherSineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(anotherSineWaveObject.name)
});
layoutGridHolder = page.locator('.l-layout__grid-holder');
// eslint-disable-next-line playwright/no-force-option
await anotherSineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true });
await page.getByText('View type').click();
await page.getByText('Overlay Plot').click();
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Time to inspect some network traffic
let networkRequests = [];
page.on('request', (request) => {
const searchRequest = request.url().endsWith('_find');
const fetchRequest = request.resourceType() === 'fetch';
if (searchRequest && fetchRequest) {
networkRequests.push(request);
}
});
await page.reload();
// wait for annotations requests to be batched and requested
await page.waitForLoadState('networkidle');
// Network requests for the composite telemetry with multiple items should be:
// 1. a single batched request for annotations
expect(networkRequests.length).toBe(1);
});
});
/**

View File

@ -27,7 +27,13 @@
// If the above namespace is ever resolved, we can fold this search provider
// back into the object provider.
const BATCH_ANNOTATION_DEBOUNCE_MS = 100;
class CouchSearchProvider {
#bulkPromise;
#batchIds;
#lastAbortSignal;
constructor(couchObjectProvider) {
this.couchObjectProvider = couchObjectProvider;
this.searchTypes = couchObjectProvider.openmct.objects.SEARCH_TYPES;
@ -36,6 +42,8 @@ class CouchSearchProvider {
this.searchTypes.ANNOTATIONS,
this.searchTypes.TAGS
];
this.#batchIds = [];
this.#bulkPromise = null;
}
supportsSearchType(searchType) {
@ -68,28 +76,77 @@ class CouchSearchProvider {
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
}
searchForAnnotations(keyString, abortSignal) {
async #deferBatchAnnotationSearch() {
// We until the next event loop cycle to "collect" all of the get
// requests triggered in this iteration of the event loop
await this.#waitForDebounce();
const batchIdsToSearch = [...this.#batchIds];
this.#clearBatch();
return this.#bulkAnnotationSearch(batchIdsToSearch);
}
#clearBatch() {
this.#batchIds = [];
this.#bulkPromise = undefined;
}
#waitForDebounce() {
let timeoutID;
clearTimeout(timeoutID);
return new Promise((resolve) => {
timeoutID = setTimeout(() => {
resolve();
}, BATCH_ANNOTATION_DEBOUNCE_MS);
});
}
#bulkAnnotationSearch(batchIdsToSearch) {
const filter = {
selector: {
$and: [
{
model: {
targets: {}
}
},
{
'model.type': {
$eq: 'annotation'
}
},
{
$or: []
}
]
}
};
filter.selector.$and[0].model.targets[keyString] = {
$exists: true
};
let lastAbortSignal = null;
// TODO: should remove duplicates from batchIds
batchIdsToSearch.forEach(({ keyString, abortSignal }) => {
const modelFilter = {
model: {
targets: {}
}
};
modelFilter.model.targets[keyString] = {
$exists: true
};
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
filter.selector.$and[1].$or.push(modelFilter);
lastAbortSignal = abortSignal;
});
return this.couchObjectProvider.getObjectsByFilter(filter, lastAbortSignal);
}
async searchForAnnotations(keyString, abortSignal) {
this.#batchIds.push({ keyString, abortSignal });
if (!this.#bulkPromise) {
this.#bulkPromise = this.#deferBatchAnnotationSearch();
}
const returnedData = await this.#bulkPromise;
// only return data that matches the keystring
const filteredByKeyString = returnedData.filter((foundAnnotation) => {
return foundAnnotation.targets[keyString];
});
return filteredByKeyString;
}
searchForTags(tagsArray, abortSignal) {