mirror of
https://github.com/nasa/openmct.git
synced 2025-02-06 11:09:21 +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;
|
return tagsAddedToResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #addTargetModelsToResults(results) {
|
async #addTargetModelsToResults(results, abortSignal) {
|
||||||
const modelAddedToResults = await Promise.all(
|
const modelAddedToResults = await Promise.all(
|
||||||
results.map(async (result) => {
|
results.map(async (result) => {
|
||||||
const targetModels = await Promise.all(
|
const targetModels = await Promise.all(
|
||||||
result.targets.map(async (target) => {
|
result.targets.map(async (target) => {
|
||||||
const targetID = target.keyString;
|
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 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 {
|
return {
|
||||||
originalPath: originalPathObjects,
|
originalPath: originalPathObjects,
|
||||||
@ -442,7 +446,7 @@ export default class AnnotationAPI extends EventEmitter {
|
|||||||
* @param {Object} [abortController] An optional abort method to stop the query
|
* @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
|
* @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);
|
const matchingTagKeys = this.#getMatchingTags(query);
|
||||||
if (!matchingTagKeys.length) {
|
if (!matchingTagKeys.length) {
|
||||||
return [];
|
return [];
|
||||||
@ -452,7 +456,7 @@ export default class AnnotationAPI extends EventEmitter {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.openmct.objects.search(
|
this.openmct.objects.search(
|
||||||
matchingTagKeys,
|
matchingTagKeys,
|
||||||
abortController,
|
abortSignal,
|
||||||
this.openmct.objects.SEARCH_TYPES.TAGS
|
this.openmct.objects.SEARCH_TYPES.TAGS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -465,7 +469,10 @@ export default class AnnotationAPI extends EventEmitter {
|
|||||||
combinedSameTargets,
|
combinedSameTargets,
|
||||||
matchingTagKeys
|
matchingTagKeys
|
||||||
);
|
);
|
||||||
const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults);
|
const appliedTargetsModels = await this.#addTargetModelsToResults(
|
||||||
|
appliedTagSearchResults,
|
||||||
|
abortSignal
|
||||||
|
);
|
||||||
const resultsWithValidPath = appliedTargetsModels.filter((result) => {
|
const resultsWithValidPath = appliedTargetsModels.filter((result) => {
|
||||||
return this.openmct.objects.isReachable(result.targetModels?.[0]?.originalPath);
|
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
|
* Given an identifier, constructs the original path by walking up its parents
|
||||||
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
* @param {module:openmct.ObjectAPI~Identifier} identifier
|
||||||
* @param {Array<module:openmct.DomainObject>} path an array of path objects
|
* @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
|
* @returns {Promise<Array<module:openmct.DomainObject>>} a promise containing an array of domain objects
|
||||||
*/
|
*/
|
||||||
async getOriginalPath(identifier, path = []) {
|
async getOriginalPath(identifier, path = [], abortSignal = null) {
|
||||||
const domainObject = await this.get(identifier);
|
const domainObject = await this.get(identifier, abortSignal);
|
||||||
path.push(domainObject);
|
path.push(domainObject);
|
||||||
const { location } = domainObject;
|
const { location } = domainObject;
|
||||||
if (location && !this.#pathContainsDomainObject(location, path)) {
|
if (location && !this.#pathContainsDomainObject(location, path)) {
|
||||||
// if we have a location, and we don't already have this in our constructed path,
|
// if we have a location, and we don't already have this in our constructed path,
|
||||||
// then keep walking up the path
|
// then keep walking up the path
|
||||||
return this.getOriginalPath(utils.parseKeyString(location), path);
|
return this.getOriginalPath(utils.parseKeyString(location), path, abortSignal);
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
this.abortController = new AbortController();
|
||||||
this.nameChangeListeners = {};
|
this.nameChangeListeners = {};
|
||||||
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
@ -87,7 +88,18 @@ export default {
|
|||||||
|
|
||||||
let rawPath = null;
|
let rawPath = null;
|
||||||
if (this.objectPath === 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 {
|
} else {
|
||||||
rawPath = this.objectPath;
|
rawPath = this.objectPath;
|
||||||
}
|
}
|
||||||
@ -115,6 +127,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort();
|
||||||
|
}
|
||||||
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
Object.values(this.nameChangeListeners).forEach((unlisten) => {
|
||||||
unlisten();
|
unlisten();
|
||||||
});
|
});
|
||||||
|
@ -104,7 +104,7 @@ export default {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getPathsForObjects(objectsNeedingPaths) {
|
getPathsForObjects(objectsNeedingPaths, abortSignal) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
objectsNeedingPaths.map(async (domainObject) => {
|
objectsNeedingPaths.map(async (domainObject) => {
|
||||||
if (!domainObject) {
|
if (!domainObject) {
|
||||||
@ -114,7 +114,9 @@ export default {
|
|||||||
|
|
||||||
const keyStringForObject = this.openmct.objects.makeKeyString(domainObject.identifier);
|
const keyStringForObject = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
const originalPathObjects = await this.openmct.objects.getOriginalPath(
|
const originalPathObjects = await this.openmct.objects.getOriginalPath(
|
||||||
keyStringForObject
|
keyStringForObject,
|
||||||
|
[],
|
||||||
|
abortSignal
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -130,44 +132,55 @@ export default {
|
|||||||
this.searchLoading = true;
|
this.searchLoading = true;
|
||||||
this.$refs.searchResultsDropDown.showSearchStarted();
|
this.$refs.searchResultsDropDown.showSearchStarted();
|
||||||
this.abortSearchController = new AbortController();
|
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);
|
try {
|
||||||
}
|
const searchObjectsPromise = this.searchObjects(this.abortSearchController.signal);
|
||||||
);
|
const searchAnnotationsPromise = this.searchAnnotations(this.abortSearchController.signal);
|
||||||
this.objectSearchResults = filterAnnotationsAndValidPaths;
|
|
||||||
|
// Wait for all promises, but they process their results as they complete
|
||||||
|
await Promise.allSettled([searchObjectsPromise, searchAnnotationsPromise]);
|
||||||
|
|
||||||
this.searchLoading = false;
|
this.searchLoading = false;
|
||||||
this.showSearchResults();
|
this.showSearchResults();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.searchLoading = false;
|
this.searchLoading = false;
|
||||||
|
|
||||||
if (this.abortSearchController) {
|
|
||||||
delete this.abortSearchController;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this coming from the AbortController?
|
// Is this coming from the AbortController?
|
||||||
// If so, we can swallow the error. If not, 🤮 it to console
|
// If so, we can swallow the error. If not, 🤮 it to console
|
||||||
if (error.name !== 'AbortError') {
|
if (error.name !== 'AbortError') {
|
||||||
console.error(`😞 Error searching`, error);
|
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() {
|
showSearchResults() {
|
||||||
const dropdownOptions = {
|
const dropdownOptions = {
|
||||||
|
@ -247,7 +247,7 @@ describe('GrandSearch', () => {
|
|||||||
// eslint-disable-next-line require-await
|
// eslint-disable-next-line require-await
|
||||||
mockObjectProvider.search = async (query, abortSignal, searchType) => {
|
mockObjectProvider.search = async (query, abortSignal, searchType) => {
|
||||||
if (searchType === openmct.objects.SEARCH_TYPES.OBJECTS) {
|
if (searchType === openmct.objects.SEARCH_TYPES.OBJECTS) {
|
||||||
return mockNewObject;
|
return [mockNewObject];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user