diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js index 72f3d74d95..f13a26de58 100644 --- a/src/api/annotation/AnnotationAPI.js +++ b/src/api/annotation/AnnotationAPI.js @@ -76,6 +76,9 @@ const ANNOTATION_LAST_CREATED = 'annotationLastCreated'; * @constructor */ export default class AnnotationAPI extends EventEmitter { + /** @type {Map boolean >>} */ + #targetComparatorMap; + /** * @param {OpenMCT} openmct */ @@ -84,6 +87,7 @@ export default class AnnotationAPI extends EventEmitter { this.openmct = openmct; this.availableTags = {}; this.namespaceToSaveAnnotations = ''; + this.#targetComparatorMap = new Map(); this.ANNOTATION_TYPES = ANNOTATION_TYPES; this.ANNOTATION_TYPE = ANNOTATION_TYPE; @@ -246,15 +250,16 @@ export default class AnnotationAPI extends EventEmitter { /** * @method getAnnotations * @param {Identifier} domainObjectIdentifier - The domain object identifier to use to search for annotations. For example, a telemetry object identifier. + * @param {AbortSignal} abortSignal - An abort signal to cancel the search * @returns {DomainObject[]} Returns an array of annotations that match the search query */ - async getAnnotations(domainObjectIdentifier) { + async getAnnotations(domainObjectIdentifier, abortSignal = null) { const keyStringQuery = this.openmct.objects.makeKeyString(domainObjectIdentifier); const searchResults = ( await Promise.all( this.openmct.objects.search( keyStringQuery, - null, + abortSignal, this.openmct.objects.SEARCH_TYPES.ANNOTATIONS ) ) @@ -384,7 +389,8 @@ export default class AnnotationAPI extends EventEmitter { const combinedResults = []; results.forEach((currentAnnotation) => { const existingAnnotation = combinedResults.find((annotationToFind) => { - return _.isEqual(currentAnnotation.targets, annotationToFind.targets); + const { annotationType, targets } = currentAnnotation; + return this.areAnnotationTargetsEqual(annotationType, targets, annotationToFind.targets); }); if (!existingAnnotation) { combinedResults.push(currentAnnotation); @@ -460,4 +466,35 @@ export default class AnnotationAPI extends EventEmitter { return breakApartSeparateTargets; } + + /** + * Adds a comparator function for a given annotation type. + * The comparator functions will be used to determine if two annotations + * have the same target. + * @param {ANNOTATION_TYPES} annotationType + * @param {(t1, t2) => boolean} comparator + */ + addTargetComparator(annotationType, comparator) { + const comparatorList = this.#targetComparatorMap.get(annotationType) ?? []; + comparatorList.push(comparator); + this.#targetComparatorMap.set(annotationType, comparatorList); + } + + /** + * Compare two sets of targets to see if they are equal. First checks if + * any targets comparators evaluate to true, then falls back to a deep + * equality check. + * @param {ANNOTATION_TYPES} annotationType + * @param {*} targets + * @param {*} otherTargets + * @returns true if the targets are equal, false otherwise + */ + areAnnotationTargetsEqual(annotationType, targets, otherTargets) { + const targetComparatorList = this.#targetComparatorMap.get(annotationType); + return ( + (targetComparatorList?.length && + targetComparatorList.some((targetComparator) => targetComparator(targets, otherTargets))) || + _.isEqual(targets, otherTargets) + ); + } } diff --git a/src/api/annotation/AnnotationAPISpec.js b/src/api/annotation/AnnotationAPISpec.js index 7be711d235..318220ba2e 100644 --- a/src/api/annotation/AnnotationAPISpec.js +++ b/src/api/annotation/AnnotationAPISpec.js @@ -265,4 +265,52 @@ describe('The Annotation API', () => { expect(results.length).toEqual(0); }); }); + + describe('Target Comparators', () => { + let targets; + let otherTargets; + let comparator; + + beforeEach(() => { + targets = { + fooTarget: { + foo: 42 + } + }; + otherTargets = { + fooTarget: { + bar: 42 + } + }; + comparator = (t1, t2) => t1.fooTarget.foo === t2.fooTarget.bar; + }); + + it('can add a comparator function', () => { + const notebookAnnotationType = openmct.annotation.ANNOTATION_TYPES.NOTEBOOK; + expect( + openmct.annotation.areAnnotationTargetsEqual(notebookAnnotationType, targets, otherTargets) + ).toBeFalse(); // without a comparator, these should NOT be equal + // Register a comparator function for the notebook annotation type + openmct.annotation.addTargetComparator(notebookAnnotationType, comparator); + expect( + openmct.annotation.areAnnotationTargetsEqual(notebookAnnotationType, targets, otherTargets) + ).toBeTrue(); // the comparator should make these equal + }); + + it('falls back to deep equality check if no comparator functions', () => { + const annotationTypeWithoutComparator = openmct.annotation.ANNOTATION_TYPES.GEOSPATIAL; + const areEqual = openmct.annotation.areAnnotationTargetsEqual( + annotationTypeWithoutComparator, + targets, + targets + ); + const areNotEqual = openmct.annotation.areAnnotationTargetsEqual( + annotationTypeWithoutComparator, + targets, + otherTargets + ); + expect(areEqual).toBeTrue(); + expect(areNotEqual).toBeFalse(); + }); + }); }); diff --git a/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue b/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue index fcf2f29653..5e990943fc 100644 --- a/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue +++ b/src/plugins/inspectorViews/annotations/AnnotationsInspectorView.vue @@ -41,7 +41,6 @@