mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 05:37:53 +00:00
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:
commit
92cba55f88
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user