Search fix identifier (#4947)

* use identifier not key for object get calls
* re-index on composition or name changes only
* search should account for namespaces

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
This commit is contained in:
David Tsay 2022-04-22 15:15:37 -07:00 committed by GitHub
parent a53a3a0297
commit cf6bc5be2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 71 deletions

View File

@ -36,13 +36,14 @@ class InMemorySearchProvider {
*/
this.MAX_CONCURRENT_REQUESTS = 100;
/**
* If max results is not specified in query, use this as default.
*/
* If max results is not specified in query, use this as default.
*/
this.DEFAULT_MAX_RESULTS = 100;
this.openmct = openmct;
this.indexedIds = {};
this.indexedCompositions = {};
this.idsToIndex = [];
this.pendingIndex = {};
this.pendingRequests = 0;
@ -58,7 +59,6 @@ class InMemorySearchProvider {
this.onWorkerMessageError = this.onWorkerMessageError.bind(this);
this.onerror = this.onWorkerError.bind(this);
this.startIndexing = this.startIndexing.bind(this);
this.onMutationOfIndexedObject = this.onMutationOfIndexedObject.bind(this);
this.openmct.on('start', this.startIndexing);
this.openmct.on('destroy', () => {
@ -68,6 +68,9 @@ class InMemorySearchProvider {
this.worker.port.onmessageerror = null;
this.worker.port.close();
}
this.destroyObservers(this.indexedIds);
this.destroyObservers(this.indexedCompositions);
});
}
@ -137,7 +140,7 @@ class InMemorySearchProvider {
};
modelResults.hits = await Promise.all(event.data.results.map(async (hit) => {
const identifier = this.openmct.objects.parseKeyString(hit.keyString);
const domainObject = await this.openmct.objects.get(identifier.key);
const domainObject = await this.openmct.objects.get(identifier);
return domainObject;
}));
@ -213,29 +216,52 @@ class InMemorySearchProvider {
}
}
onMutationOfIndexedObject(domainObject) {
onNameMutation(domainObject, name) {
const provider = this;
provider.index(domainObject.identifier, domainObject);
domainObject.name = name;
provider.index(domainObject);
}
onCompositionMutation(domainObject, composition) {
const provider = this;
const indexedComposition = domainObject.composition;
const identifiersToIndex = composition
.filter(identifier => !indexedComposition
.some(indexedIdentifier => this.openmct.objects
.areIdsEqual([identifier, indexedIdentifier])));
identifiersToIndex.forEach(identifier => {
this.openmct.objects.get(identifier).then(objectToIndex => provider.index(objectToIndex));
});
}
/**
* Pass an id and model to the worker to be indexed. If the model has
* composition, schedule those ids for later indexing.
* Pass a domainObject to the worker to be indexed.
* If the object has composition, schedule those ids for later indexing.
* Watch for object changes and re-index object and children if so
*
* @private
* @param id a model id
* @param model a model
* @param domainObject a domainObject
*/
async index(id, domainObject) {
async index(domainObject) {
const provider = this;
const keyString = this.openmct.objects.makeKeyString(id);
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.indexedIds[keyString]) {
this.openmct.objects.observe(domainObject, `*`, this.onMutationOfIndexedObject);
this.indexedIds[keyString] = this.openmct.objects.observe(
domainObject,
'name',
this.onNameMutation.bind(this, domainObject)
);
this.indexedCompositions[keyString] = this.openmct.objects.observe(
domainObject,
'composition',
this.onCompositionMutation.bind(this, domainObject)
);
}
this.indexedIds[keyString] = true;
if ((id.key !== 'ROOT')) {
if ((keyString !== 'ROOT')) {
if (this.worker) {
this.worker.port.postMessage({
request: 'index',
@ -247,15 +273,12 @@ class InMemorySearchProvider {
}
}
const composition = this.openmct.composition.registry.find(foundComposition => {
return foundComposition.appliesTo(domainObject);
});
const composition = this.openmct.composition.get(domainObject);
if (composition) {
const childIdentifiers = await composition.load(domainObject);
childIdentifiers.forEach(function (childIdentifier) {
provider.scheduleForIndexing(childIdentifier);
});
if (composition !== undefined) {
const children = await composition.load();
children.forEach(child => provider.scheduleForIndexing(child.identifier));
}
}
@ -271,12 +294,12 @@ class InMemorySearchProvider {
const provider = this;
this.pendingRequests += 1;
const identifier = await this.openmct.objects.parseKeyString(keyString);
const domainObject = await this.openmct.objects.get(identifier.key);
const domainObject = await this.openmct.objects.get(keyString);
delete provider.pendingIndex[keyString];
try {
if (domainObject) {
await provider.index(identifier, domainObject);
await provider.index(domainObject);
}
} catch (error) {
console.warn('Failed to index domain object ' + keyString, error);
@ -305,9 +328,9 @@ class InMemorySearchProvider {
}
/**
* A local version of the same SharedWorker function
* if we don't have SharedWorkers available (e.g., iOS)
*/
* A local version of the same SharedWorker function
* if we don't have SharedWorkers available (e.g., iOS)
*/
localIndexItem(keyString, model) {
this.localIndexedItems[keyString] = {
type: model.type,
@ -347,6 +370,16 @@ class InMemorySearchProvider {
};
this.onWorkerMessage(eventToReturn);
}
destroyObservers(observers) {
Object.entries(observers).forEach(([keyString, unobserve]) => {
if (typeof unobserve === 'function') {
unobserve();
}
delete observers[keyString];
});
}
}
export default InMemorySearchProvider;

View File

@ -105,13 +105,18 @@ describe("The Object API Search Function", () => {
beforeEach((done) => {
openmct = createOpenMct();
const defaultObjectProvider = openmct.objects.getProvider({
key: '',
namespace: ''
});
openmct.objects.addProvider('foo', defaultObjectProvider);
spyOn(openmct.objects.inMemorySearchProvider, "query").and.callThrough();
spyOn(openmct.objects.inMemorySearchProvider, "localSearch").and.callThrough();
openmct.on('start', async () => {
mockIdentifier1 = {
key: 'some-object',
namespace: 'some-namespace'
namespace: 'foo'
};
mockDomainObject1 = {
type: 'clock',
@ -120,7 +125,7 @@ describe("The Object API Search Function", () => {
};
mockIdentifier2 = {
key: 'some-other-object',
namespace: 'some-namespace'
namespace: 'foo'
};
mockDomainObject2 = {
type: 'clock',
@ -129,16 +134,16 @@ describe("The Object API Search Function", () => {
};
mockIdentifier3 = {
key: 'yet-another-object',
namespace: 'some-namespace'
namespace: 'foo'
};
mockDomainObject3 = {
type: 'clock',
name: 'redBear',
identifier: mockIdentifier3
};
await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1);
await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2);
await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject1);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject2);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject3);
done();
});
openmct.startHeadless();
@ -175,9 +180,9 @@ describe("The Object API Search Function", () => {
beforeEach(async () => {
openmct.objects.inMemorySearchProvider.worker = null;
// reindex locally
await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1);
await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2);
await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject1);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject2);
await openmct.objects.inMemorySearchProvider.index(mockDomainObject3);
});
it("calls local search", () => {
openmct.objects.search('foo');

View File

@ -51,41 +51,29 @@ export default class EditPropertiesAction extends PropertiesAction {
/**
* @private
*/
async _onSave(changes) {
Object.entries(changes).forEach(([key, value]) => {
const properties = key.split('.');
let object = this.domainObject;
const propertiesLength = properties.length;
properties.forEach((property, index) => {
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
if (isComplexProperty && object[property] !== null) {
object = object[property];
} else {
object[property] = value;
}
_onSave(changes) {
try {
Object.entries(changes).forEach(([key, value]) => {
const properties = key.split('.');
let object = this.domainObject;
const propertiesLength = properties.length;
properties.forEach((property, index) => {
const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1;
if (isComplexProperty && object[property] !== null) {
object = object[property];
} else {
object[property] = value;
}
});
object = value;
this.openmct.objects.mutate(this.domainObject, key, value);
this.openmct.notifications.info('Save successful');
});
object = value;
});
this.domainObject.modified = Date.now();
// Show saving progress dialog
let dialog = this.openmct.overlays.progressDialog({
progressPerc: 'unknown',
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
iconClass: 'info',
title: 'Saving'
});
const success = await this.openmct.objects.save(this.domainObject);
if (success) {
this.openmct.notifications.info('Save successful');
} else {
} catch (error) {
this.openmct.notifications.error('Error saving objects');
console.error(error);
}
dialog.dismiss();
}
/**