mirror of
https://github.com/nasa/openmct.git
synced 2025-04-16 07:26:53 +00:00
[Abort Search] Ability to Cancel Search Requests (#3716)
* adding first triggers for aborting search * adding abort capabilities to the path a search request takes through the code * switching empty args from null to undefined * adding abortSignal to couchdb provider request function * minor syntax tweak * fixing accidental change of code * simplifying the assignment of fetch options * add finally to search promises to delete abort controller just in case it is still there * passing signal in to provider.get not getProvider * moving the couchdb doc creation out of the argument for request * removing console log for aborted search error * lint fix * adding interceptors to objects.search * removing the options object and replacing with abort signal * removing unused variable leftover * had accidentally removed stringifying the body of the request if present... added back in * created an applyGetInterceptors function for search and get to use * created an applyGetInterceptors function for search and get to use * fixed bug that our TESTS FOUND!!!!
This commit is contained in:
parent
201d622b85
commit
5d656f0963
@ -37,7 +37,7 @@ define(
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
LocatingObjectDecorator.prototype.getObjects = function (ids) {
|
||||
LocatingObjectDecorator.prototype.getObjects = function (ids, abortSignal) {
|
||||
var $q = this.$q,
|
||||
$log = this.$log,
|
||||
objectService = this.objectService,
|
||||
@ -79,7 +79,7 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
return objectService.getObjects([id]).then(attachContext);
|
||||
return objectService.getObjects([id], abortSignal).then(attachContext);
|
||||
}
|
||||
|
||||
ids.forEach(function (id) {
|
||||
|
@ -80,12 +80,15 @@ define([
|
||||
* @param {Function} [filter] if provided, will be called for every
|
||||
* potential modelResult. If it returns false, the model result will be
|
||||
* excluded from the search results.
|
||||
* @param {AbortController.signal} abortSignal (optional) can pass in an abortSignal to cancel any
|
||||
* downstream fetch requests.
|
||||
* @returns {Promise} A Promise for a search result object.
|
||||
*/
|
||||
SearchAggregator.prototype.query = function (
|
||||
inputText,
|
||||
maxResults,
|
||||
filter
|
||||
filter,
|
||||
abortSignal
|
||||
) {
|
||||
|
||||
var aggregator = this,
|
||||
@ -120,7 +123,7 @@ define([
|
||||
modelResults = aggregator.applyFilter(modelResults, filter);
|
||||
modelResults = aggregator.removeDuplicates(modelResults);
|
||||
|
||||
return aggregator.asObjectResults(modelResults);
|
||||
return aggregator.asObjectResults(modelResults, abortSignal);
|
||||
});
|
||||
};
|
||||
|
||||
@ -193,16 +196,19 @@ define([
|
||||
* Convert modelResults to objectResults by fetching them from the object
|
||||
* service.
|
||||
*
|
||||
* @param {Object} modelResults an object containing the results from the search
|
||||
* @param {AbortController.signal} abortSignal (optional) abort signal to cancel any
|
||||
* downstream fetch requests
|
||||
* @returns {Promise} for an objectResults object.
|
||||
*/
|
||||
SearchAggregator.prototype.asObjectResults = function (modelResults) {
|
||||
SearchAggregator.prototype.asObjectResults = function (modelResults, abortSignal) {
|
||||
var objectIds = modelResults.hits.map(function (modelResult) {
|
||||
return modelResult.id;
|
||||
});
|
||||
|
||||
return this
|
||||
.objectService
|
||||
.getObjects(objectIds)
|
||||
.getObjects(objectIds, abortSignal)
|
||||
.then(function (objects) {
|
||||
|
||||
var objectResults = {
|
||||
|
@ -139,10 +139,12 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.superSecretFallbackSearch = function (query, options) {
|
||||
ObjectServiceProvider.prototype.superSecretFallbackSearch = function (query, abortSignal) {
|
||||
const searchService = this.$injector.get('searchService');
|
||||
|
||||
return searchService.query(query);
|
||||
// need to pass the abortSignal down, so need to
|
||||
// pass in undefined for maxResults and filter on query
|
||||
return searchService.query(query, undefined, undefined, abortSignal);
|
||||
};
|
||||
|
||||
// Injects new object API as a decorator so that it hijacks all requests.
|
||||
@ -150,13 +152,13 @@ define([
|
||||
function LegacyObjectAPIInterceptor(openmct, ROOTS, instantiate, topic, objectService) {
|
||||
const eventEmitter = openmct.objects.eventEmitter;
|
||||
|
||||
this.getObjects = function (keys) {
|
||||
this.getObjects = function (keys, abortSignal) {
|
||||
const results = {};
|
||||
|
||||
const promises = keys.map(function (keyString) {
|
||||
const key = utils.parseKeyString(keyString);
|
||||
|
||||
return openmct.objects.get(key)
|
||||
return openmct.objects.get(key, abortSignal)
|
||||
.then(function (object) {
|
||||
object = utils.toOldFormat(object);
|
||||
results[keyString] = instantiate(object, keyString);
|
||||
|
@ -154,11 +154,12 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
||||
* @method get
|
||||
* @memberof module:openmct.ObjectProvider#
|
||||
* @param {string} key the key for the domain object to load
|
||||
* @param {AbortController.signal} abortSignal (optional) signal to abort fetch requests
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
|
||||
ObjectAPI.prototype.get = function (identifier) {
|
||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
@ -175,15 +176,12 @@ ObjectAPI.prototype.get = function (identifier) {
|
||||
throw new Error('Provider does not support get!');
|
||||
}
|
||||
|
||||
let objectPromise = provider.get(identifier);
|
||||
let objectPromise = provider.get(identifier, abortSignal);
|
||||
this.cache[keystring] = objectPromise;
|
||||
|
||||
return objectPromise.then(result => {
|
||||
delete this.cache[keystring];
|
||||
const interceptors = this.listGetInterceptors(identifier, result);
|
||||
interceptors.forEach(interceptor => {
|
||||
result = interceptor.invoke(identifier, result);
|
||||
});
|
||||
result = this.applyGetInterceptors(identifier, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
@ -200,19 +198,24 @@ ObjectAPI.prototype.get = function (identifier) {
|
||||
* @method search
|
||||
* @memberof module:openmct.ObjectAPI#
|
||||
* @param {string} query the term to search for
|
||||
* @param {Object} options search options
|
||||
* @param {AbortController.signal} abortSignal (optional) signal to cancel downstream fetch requests
|
||||
* @returns {Array.<Promise.<module:openmct.DomainObject>>}
|
||||
* an array of promises returned from each object provider's search function
|
||||
* each resolving to domain objects matching provided search query and options.
|
||||
*/
|
||||
ObjectAPI.prototype.search = function (query, options) {
|
||||
ObjectAPI.prototype.search = function (query, abortSignal) {
|
||||
const searchPromises = Object.values(this.providers)
|
||||
.filter(provider => provider.search !== undefined)
|
||||
.map(provider => provider.search(query, options));
|
||||
.map(provider => provider.search(query, abortSignal));
|
||||
|
||||
searchPromises.push(this.fallbackProvider.superSecretFallbackSearch(query, options)
|
||||
searchPromises.push(this.fallbackProvider.superSecretFallbackSearch(query, abortSignal)
|
||||
.then(results => results.hits
|
||||
.map(hit => utils.toNewFormat(hit.object.getModel(), hit.object.getId()))));
|
||||
.map(hit => {
|
||||
let domainObject = utils.toNewFormat(hit.object.getModel(), hit.object.getId());
|
||||
domainObject = this.applyGetInterceptors(domainObject.identifier, domainObject);
|
||||
|
||||
return domainObject;
|
||||
})));
|
||||
|
||||
return searchPromises;
|
||||
};
|
||||
@ -338,6 +341,19 @@ ObjectAPI.prototype.listGetInterceptors = function (identifier, object) {
|
||||
return this.interceptorRegistry.getInterceptors(identifier, object);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inovke interceptors if applicable for a given domain object.
|
||||
* @private
|
||||
*/
|
||||
ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
|
||||
const interceptors = this.listGetInterceptors(identifier, domainObject);
|
||||
interceptors.forEach(interceptor => {
|
||||
domainObject = interceptor.invoke(identifier, domainObject);
|
||||
});
|
||||
|
||||
return domainObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Modify a domain object.
|
||||
* @param {module:openmct.DomainObject} object the object to mutate
|
||||
|
@ -57,11 +57,20 @@ export default class CouchObjectProvider {
|
||||
return options;
|
||||
}
|
||||
|
||||
request(subPath, method, value) {
|
||||
return fetch(this.url + '/' + subPath, {
|
||||
method: method,
|
||||
body: JSON.stringify(value)
|
||||
}).then(response => response.json())
|
||||
request(subPath, method, body, signal) {
|
||||
let fetchOptions = {
|
||||
method,
|
||||
body,
|
||||
signal
|
||||
};
|
||||
|
||||
// stringify body if needed
|
||||
if (fetchOptions.body) {
|
||||
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
||||
}
|
||||
|
||||
return fetch(this.url + '/' + subPath, fetchOptions)
|
||||
.then(response => response.json())
|
||||
.then(function (response) {
|
||||
return response;
|
||||
}, function () {
|
||||
@ -121,8 +130,8 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
}
|
||||
|
||||
get(identifier) {
|
||||
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
|
||||
get(identifier, abortSignal) {
|
||||
return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
|
||||
}
|
||||
|
||||
async getObjectsByFilter(filter) {
|
||||
@ -313,7 +322,8 @@ export default class CouchObjectProvider {
|
||||
this.enqueueObject(key, model, intermediateResponse);
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
this.request(key, "PUT", new CouchDocument(key, queued.model)).then((response) => {
|
||||
let document = new CouchDocument(key, queued.model);
|
||||
this.request(key, "PUT", document).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
});
|
||||
|
||||
@ -324,7 +334,8 @@ export default class CouchObjectProvider {
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
this.request(key, "PUT", new CouchDocument(key, queued.model, this.objectQueue[key].rev)).then((response) => {
|
||||
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
||||
this.request(key, "PUT", document).then((response) => {
|
||||
this.checkResponse(response, queued.intermediateResponse);
|
||||
});
|
||||
}
|
||||
|
@ -235,6 +235,12 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
syncTreeNavigation() {
|
||||
// if there is an abort controller, then a search is in progress and will need to be canceled
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
delete this.abortController;
|
||||
}
|
||||
|
||||
this.searchValue = '';
|
||||
|
||||
if (!this.openmct.router.path) {
|
||||
@ -685,12 +691,23 @@ export default {
|
||||
// clear any previous search results
|
||||
this.searchResultItems = [];
|
||||
|
||||
const promises = this.openmct.objects.search(this.searchValue)
|
||||
// an abort controller will be passed in that will be used
|
||||
// to cancel an active searches if necessary
|
||||
this.abortController = new AbortController();
|
||||
const abortSignal = this.abortController.signal;
|
||||
|
||||
const promises = this.openmct.objects.search(this.searchValue, abortSignal)
|
||||
.map(promise => promise
|
||||
.then(results => this.aggregateSearchResults(results)));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.searchLoading = false;
|
||||
}).catch(reason => {
|
||||
// search aborted
|
||||
}).finally(() => {
|
||||
if (this.abortController) {
|
||||
delete this.abortController;
|
||||
}
|
||||
});
|
||||
},
|
||||
async aggregateSearchResults(results) {
|
||||
@ -714,6 +731,13 @@ export default {
|
||||
}
|
||||
},
|
||||
searchTree(value) {
|
||||
// if an abort controller exists, regardless of the value passed in,
|
||||
// there is an active search that should be cancled
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
delete this.abortController;
|
||||
}
|
||||
|
||||
this.searchValue = value;
|
||||
this.searchLoading = true;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user