mirror of
https://github.com/nasa/openmct.git
synced 2025-01-30 16:13:53 +00:00
Batch annotation requests (#6719)
* batching, but query is messed up * batching requests * remove debug statement * add test * revert couchdb change
This commit is contained in:
parent
9a01cee5fa
commit
fb5bbde154
@ -205,6 +205,71 @@ test.describe('Display Layout', () => {
|
|||||||
|
|
||||||
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
|
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
|
// If the above namespace is ever resolved, we can fold this search provider
|
||||||
// back into the object provider.
|
// back into the object provider.
|
||||||
|
|
||||||
|
const BATCH_ANNOTATION_DEBOUNCE_MS = 100;
|
||||||
|
|
||||||
class CouchSearchProvider {
|
class CouchSearchProvider {
|
||||||
|
#bulkPromise;
|
||||||
|
#batchIds;
|
||||||
|
#lastAbortSignal;
|
||||||
|
|
||||||
constructor(couchObjectProvider) {
|
constructor(couchObjectProvider) {
|
||||||
this.couchObjectProvider = couchObjectProvider;
|
this.couchObjectProvider = couchObjectProvider;
|
||||||
this.searchTypes = couchObjectProvider.openmct.objects.SEARCH_TYPES;
|
this.searchTypes = couchObjectProvider.openmct.objects.SEARCH_TYPES;
|
||||||
@ -36,6 +42,8 @@ class CouchSearchProvider {
|
|||||||
this.searchTypes.ANNOTATIONS,
|
this.searchTypes.ANNOTATIONS,
|
||||||
this.searchTypes.TAGS
|
this.searchTypes.TAGS
|
||||||
];
|
];
|
||||||
|
this.#batchIds = [];
|
||||||
|
this.#bulkPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsSearchType(searchType) {
|
supportsSearchType(searchType) {
|
||||||
@ -68,28 +76,77 @@ class CouchSearchProvider {
|
|||||||
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
|
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 = {
|
const filter = {
|
||||||
selector: {
|
selector: {
|
||||||
$and: [
|
$and: [
|
||||||
{
|
|
||||||
model: {
|
|
||||||
targets: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
'model.type': {
|
'model.type': {
|
||||||
$eq: 'annotation'
|
$eq: 'annotation'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$or: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
filter.selector.$and[0].model.targets[keyString] = {
|
let lastAbortSignal = null;
|
||||||
|
// TODO: should remove duplicates from batchIds
|
||||||
|
batchIdsToSearch.forEach(({ keyString, abortSignal }) => {
|
||||||
|
const modelFilter = {
|
||||||
|
model: {
|
||||||
|
targets: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
modelFilter.model.targets[keyString] = {
|
||||||
$exists: true
|
$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) {
|
searchForTags(tagsArray, abortSignal) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user