mirror of
https://github.com/nasa/openmct.git
synced 2025-01-29 15:43:52 +00:00
When searching, build the path objects asynchronously while returning the results (#7265)
* build paths as fast as we can * fix tests * add abort controllers and async load tags
This commit is contained in:
parent
e7b9481aa9
commit
72e0621ecd
@ -366,15 +366,19 @@ export default class AnnotationAPI extends EventEmitter {
|
||||
return tagsAddedToResults;
|
||||
}
|
||||
|
||||
async #addTargetModelsToResults(results) {
|
||||
async #addTargetModelsToResults(results, abortSignal) {
|
||||
const modelAddedToResults = await Promise.all(
|
||||
results.map(async (result) => {
|
||||
const targetModels = await Promise.all(
|
||||
result.targets.map(async (target) => {
|
||||
const targetID = target.keyString;
|
||||
const targetModel = await this.openmct.objects.get(targetID);
|
||||
const targetModel = await this.openmct.objects.get(targetID, abortSignal);
|
||||
const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier);
|
||||
const originalPathObjects = await this.openmct.objects.getOriginalPath(targetKeyString);
|
||||
const originalPathObjects = await this.openmct.objects.getOriginalPath(
|
||||
targetKeyString,
|
||||
[],
|
||||
abortSignal
|
||||
);
|
||||
|
||||
return {
|
||||
originalPath: originalPathObjects,
|
||||
@ -442,7 +446,7 @@ export default class AnnotationAPI extends EventEmitter {
|
||||
* @param {Object} [abortController] An optional abort method to stop the query
|
||||
* @returns {Promise} returns a model of matching tags with their target domain objects attached
|
||||
*/
|
||||
async searchForTags(query, abortController) {
|
||||
async searchForTags(query, abortSignal) {
|
||||
const matchingTagKeys = this.#getMatchingTags(query);
|
||||
if (!matchingTagKeys.length) {
|
||||
return [];
|
||||
@ -452,7 +456,7 @@ export default class AnnotationAPI extends EventEmitter {
|
||||
await Promise.all(
|
||||
this.openmct.objects.search(
|
||||
matchingTagKeys,
|
||||
abortController,
|
||||
abortSignal,
|
||||
this.openmct.objects.SEARCH_TYPES.TAGS
|
||||
)
|
||||
)
|
||||
@ -465,7 +469,10 @@ export default class AnnotationAPI extends EventEmitter {
|
||||
combinedSameTargets,
|
||||
matchingTagKeys
|
||||
);
|
||||
const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults);
|
||||
const appliedTargetsModels = await this.#addTargetModelsToResults(
|
||||
appliedTagSearchResults,
|
||||
abortSignal
|
||||
);
|
||||
const resultsWithValidPath = appliedTargetsModels.filter((result) => {
|
||||
return this.openmct.objects.isReachable(result.targetModels?.[0]?.originalPath);
|
||||
});
|
||||
|
@ -786,16 +786,17 @@ export default class ObjectAPI {
|
||||
* Given an identifier, constructs the original path by walking up its parents
|
||||
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
||||
* @param {Array<module:openmct.DomainObject>} path an array of path objects
|
||||
* @param {AbortSignal} abortSignal (optional) signal to abort fetch requests
|
||||
* @returns {Promise<Array<module:openmct.DomainObject>>} a promise containing an array of domain objects
|
||||
*/
|
||||
async getOriginalPath(identifier, path = []) {
|
||||
const domainObject = await this.get(identifier);
|
||||
async getOriginalPath(identifier, path = [], abortSignal = null) {
|
||||
const domainObject = await this.get(identifier, abortSignal);
|
||||
path.push(domainObject);
|
||||
const { location } = domainObject;
|
||||
if (location && !this.#pathContainsDomainObject(location, path)) {
|
||||
// if we have a location, and we don't already have this in our constructed path,
|
||||
// then keep walking up the path
|
||||
return this.getOriginalPath(utils.parseKeyString(location), path);
|
||||
return this.getOriginalPath(utils.parseKeyString(location), path, abortSignal);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ export default {
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.abortController = new AbortController();
|
||||
this.nameChangeListeners = {};
|
||||
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
@ -87,7 +88,18 @@ export default {
|
||||
|
||||
let rawPath = null;
|
||||
if (this.objectPath === null) {
|
||||
rawPath = await this.openmct.objects.getOriginalPath(keyString);
|
||||
try {
|
||||
rawPath = await this.openmct.objects.getOriginalPath(
|
||||
keyString,
|
||||
[],
|
||||
this.abortController.signal
|
||||
);
|
||||
} catch (error) {
|
||||
// aborting the search is ok, everything else should be thrown
|
||||
if (error.name !== 'AbortError') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rawPath = this.objectPath;
|
||||
}
|
||||
@ -115,6 +127,9 @@ export default {
|
||||
}
|
||||
},
|
||||
unmounted() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
}
|
||||
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||
unlisten();
|
||||
});
|
||||
|
@ -104,7 +104,7 @@ export default {
|
||||
});
|
||||
};
|
||||
},
|
||||
getPathsForObjects(objectsNeedingPaths) {
|
||||
getPathsForObjects(objectsNeedingPaths, abortSignal) {
|
||||
return Promise.all(
|
||||
objectsNeedingPaths.map(async (domainObject) => {
|
||||
if (!domainObject) {
|
||||
@ -114,7 +114,9 @@ export default {
|
||||
|
||||
const keyStringForObject = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
const originalPathObjects = await this.openmct.objects.getOriginalPath(
|
||||
keyStringForObject
|
||||
keyStringForObject,
|
||||
[],
|
||||
abortSignal
|
||||
);
|
||||
|
||||
return {
|
||||
@ -130,45 +132,56 @@ export default {
|
||||
this.searchLoading = true;
|
||||
this.$refs.searchResultsDropDown.showSearchStarted();
|
||||
this.abortSearchController = new AbortController();
|
||||
const abortSignal = this.abortSearchController.signal;
|
||||
try {
|
||||
this.annotationSearchResults = await this.openmct.annotation.searchForTags(
|
||||
this.searchValue,
|
||||
abortSignal
|
||||
);
|
||||
const fullObjectSearchResults = await Promise.all(
|
||||
this.openmct.objects.search(this.searchValue, abortSignal)
|
||||
);
|
||||
const aggregatedObjectSearchResults = fullObjectSearchResults.flat();
|
||||
const aggregatedObjectSearchResultsWithPaths = await this.getPathsForObjects(
|
||||
aggregatedObjectSearchResults
|
||||
);
|
||||
const filterAnnotationsAndValidPaths = aggregatedObjectSearchResultsWithPaths.filter(
|
||||
(result) => {
|
||||
if (this.openmct.annotation.isAnnotation(result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.objects.isReachable(result?.objectPath);
|
||||
}
|
||||
);
|
||||
this.objectSearchResults = filterAnnotationsAndValidPaths;
|
||||
try {
|
||||
const searchObjectsPromise = this.searchObjects(this.abortSearchController.signal);
|
||||
const searchAnnotationsPromise = this.searchAnnotations(this.abortSearchController.signal);
|
||||
|
||||
// Wait for all promises, but they process their results as they complete
|
||||
await Promise.allSettled([searchObjectsPromise, searchAnnotationsPromise]);
|
||||
|
||||
this.searchLoading = false;
|
||||
this.showSearchResults();
|
||||
} catch (error) {
|
||||
this.searchLoading = false;
|
||||
|
||||
if (this.abortSearchController) {
|
||||
delete this.abortSearchController;
|
||||
}
|
||||
|
||||
// Is this coming from the AbortController?
|
||||
// If so, we can swallow the error. If not, 🤮 it to console
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error(`😞 Error searching`, error);
|
||||
}
|
||||
} finally {
|
||||
if (this.abortSearchController) {
|
||||
delete this.abortSearchController;
|
||||
}
|
||||
}
|
||||
},
|
||||
async searchObjects(abortSignal) {
|
||||
const objectSearchPromises = this.openmct.objects.search(this.searchValue, abortSignal);
|
||||
for await (const objectSearchResult of objectSearchPromises) {
|
||||
const objectsWithPaths = await this.getPathsForObjects(objectSearchResult, abortSignal);
|
||||
this.objectSearchResults.push(
|
||||
...objectsWithPaths.filter((result) => {
|
||||
// Check if the result is NOT an annotation and has a reachable path
|
||||
return (
|
||||
!this.openmct.annotation.isAnnotation(result) &&
|
||||
this.openmct.objects.isReachable(result?.objectPath)
|
||||
);
|
||||
})
|
||||
);
|
||||
// Display the available results so far for objects
|
||||
this.showSearchResults();
|
||||
}
|
||||
},
|
||||
async searchAnnotations(abortSignal) {
|
||||
const annotationSearchResults = await this.openmct.annotation.searchForTags(
|
||||
this.searchValue,
|
||||
abortSignal
|
||||
);
|
||||
this.annotationSearchResults = annotationSearchResults;
|
||||
// Display the available results so far for annotations
|
||||
this.showSearchResults();
|
||||
},
|
||||
showSearchResults() {
|
||||
const dropdownOptions = {
|
||||
searchLoading: this.searchLoading,
|
||||
|
@ -247,7 +247,7 @@ describe('GrandSearch', () => {
|
||||
// eslint-disable-next-line require-await
|
||||
mockObjectProvider.search = async (query, abortSignal, searchType) => {
|
||||
if (searchType === openmct.objects.SEARCH_TYPES.OBJECTS) {
|
||||
return mockNewObject;
|
||||
return [mockNewObject];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user