mirror of
https://github.com/nasa/openmct.git
synced 2025-05-30 22:24:18 +00:00
* two namespaces appear * works with two databases * try to batch requests * fix indicators * add option to omit root for myitems * ready for review * ready for review * ready for review * typo in README * spelling * spelling * update readme * fix tests * Update src/plugins/persistence/couch/README.md Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> * remove omitRoot * remove omitRoot * fix my items to check for namespace when intercepting * update readme --------- Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com> Co-authored-by: Andrew Henry <akhenry@gmail.com>
205 lines
5.9 KiB
JavaScript
205 lines
5.9 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
* Administration. All rights reserved.
|
|
*
|
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
* Open MCT includes source code licensed under additional open source
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
* this source code distribution or the Licensing information page available
|
|
* at runtime from the About dialog for additional information.
|
|
*****************************************************************************/
|
|
|
|
// This provider exists because due to legacy reasons, we need to install
|
|
// two plugins for two namespaces for CouchDB: one for "mct", and one for "".
|
|
// Because of this, we need to separate out the search provider from the object
|
|
// provider so we don't return two results for each found object.
|
|
// 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;
|
|
this.useDesignDocuments = couchObjectProvider.useDesignDocuments;
|
|
this.supportedSearchTypes = [
|
|
this.searchTypes.OBJECTS,
|
|
this.searchTypes.ANNOTATIONS,
|
|
this.searchTypes.TAGS
|
|
];
|
|
this.#batchIds = [];
|
|
this.#bulkPromise = null;
|
|
}
|
|
|
|
supportsSearchType(searchType) {
|
|
return this.supportedSearchTypes.includes(searchType);
|
|
}
|
|
|
|
isReadOnly() {
|
|
return true;
|
|
}
|
|
|
|
search(query, abortSignal, searchType) {
|
|
if (searchType === this.searchTypes.OBJECTS) {
|
|
return this.searchForObjects(query, abortSignal);
|
|
} else if (searchType === this.searchTypes.ANNOTATIONS) {
|
|
return this.searchForAnnotations(query, abortSignal);
|
|
} else if (searchType === this.searchTypes.TAGS) {
|
|
return this.searchForTags(query, abortSignal);
|
|
} else {
|
|
throw new Error(`🤷♂️ Unknown search type passed: ${searchType}`);
|
|
}
|
|
}
|
|
|
|
searchForObjects(query, abortSignal) {
|
|
const filter = {
|
|
selector: {
|
|
model: {
|
|
name: {
|
|
$regex: `(?i)${query}`
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return this.couchObjectProvider.getObjectsByFilter(filter, 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) {
|
|
if (!batchIdsToSearch?.length) {
|
|
// nothing to search
|
|
return;
|
|
}
|
|
|
|
let lastAbortSignal = batchIdsToSearch[batchIdsToSearch.length - 1].abortSignal;
|
|
|
|
if (this.useDesignDocuments) {
|
|
const keysToSearch = batchIdsToSearch.map(({ keyString }) => keyString);
|
|
return this.couchObjectProvider.getObjectsByView(
|
|
{
|
|
designDoc: 'annotation_keystring_index',
|
|
viewName: 'by_keystring',
|
|
keysToSearch
|
|
},
|
|
lastAbortSignal
|
|
);
|
|
}
|
|
|
|
const filter = {
|
|
selector: {
|
|
$and: [
|
|
{
|
|
'model.type': {
|
|
$eq: 'annotation'
|
|
}
|
|
},
|
|
{
|
|
'model.targets': {
|
|
$elemMatch: {
|
|
keyString: {
|
|
$in: []
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
// TODO: should remove duplicates from batchIds
|
|
batchIdsToSearch.forEach(({ keyString, abortSignal }) => {
|
|
filter.selector.$and[1]['model.targets'].$elemMatch.keyString.$in.push(keyString);
|
|
});
|
|
|
|
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;
|
|
return returnedData;
|
|
}
|
|
|
|
searchForTags(tagsArray, abortSignal) {
|
|
if (!tagsArray || !tagsArray.length) {
|
|
return [];
|
|
}
|
|
|
|
if (this.useDesignDocuments) {
|
|
return this.couchObjectProvider.getObjectsByView(
|
|
{ designDoc: 'annotation_tags_index', viewName: 'by_tags', keysToSearch: tagsArray },
|
|
abortSignal
|
|
);
|
|
}
|
|
|
|
const filter = {
|
|
selector: {
|
|
$and: [
|
|
{
|
|
'model.type': {
|
|
$eq: 'annotation'
|
|
}
|
|
},
|
|
{
|
|
'model.tags': {
|
|
$elemMatch: {
|
|
$in: []
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
tagsArray.forEach((tag) => {
|
|
filter.selector.$and[1]['model.tags'].$elemMatch.$in.push(tag);
|
|
});
|
|
|
|
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
|
|
}
|
|
}
|
|
export default CouchSearchProvider;
|