From e1969585f99e35e86571e029bf11ebc8b26c3c7c Mon Sep 17 00:00:00 2001 From: Jamie V Date: Fri, 20 Dec 2024 14:22:18 -0800 Subject: [PATCH 1/7] added getTypes to types api, modifying which tabs are shown, working on annoatation tab --- src/api/types/TypeRegistry.js | 11 +++++++++++ .../styles/StylesInspectorViewProvider.js | 9 +++++++++ src/plugins/notebook/NotebookType.js | 1 + 3 files changed, 21 insertions(+) diff --git a/src/api/types/TypeRegistry.js b/src/api/types/TypeRegistry.js index 9506992edb3..1dac938fb47 100644 --- a/src/api/types/TypeRegistry.js +++ b/src/api/types/TypeRegistry.js @@ -89,6 +89,17 @@ export default class TypeRegistry { get(typeKey) { return this.types[typeKey] || UNKNOWN_TYPE; } + /** + * List all registered types. + * @returns {Type[]} all registered types + */ + getTypes() { + return Object.values(this.types); + } + /** + * Import legacy types. + * @param {TypeDefinition[]} types the types to import + */ importLegacyTypes(types) { types .filter((t) => this.get(t.key) === UNKNOWN_TYPE) diff --git a/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js b/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js index 52d79afdd4e..8abc1acdc15 100644 --- a/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js +++ b/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js @@ -91,6 +91,15 @@ export default function StylesInspectorViewProvider(openmct) { ); _destroy = destroy; }, + showTab: function () { + const objectSelection = selection?.[0]; + const objectContext = objectSelection?.[0]?.context; + const layoutItem = objectContext?.layoutItem; + const domainObject = objectContext?.item; + const hasStyles = domainObject?.configuration?.objectStyles; + + return layoutItem || hasStyles; + }, priority: function () { return openmct.priority.DEFAULT; }, diff --git a/src/plugins/notebook/NotebookType.js b/src/plugins/notebook/NotebookType.js index 375c22af8af..ef299fb69c4 100644 --- a/src/plugins/notebook/NotebookType.js +++ b/src/plugins/notebook/NotebookType.js @@ -28,6 +28,7 @@ export default class NotebookType { this.description = description; this.cssClass = icon; this.creatable = true; + this.annotatable = true; this.form = [ { key: 'defaultSort', From 128f4827dfd814d0d43e34ac4fae533c5258db2a Mon Sep 17 00:00:00 2001 From: Jamie V Date: Fri, 20 Dec 2024 15:12:08 -0800 Subject: [PATCH 2/7] moving hasNumericTelemetry to an api method --- src/api/telemetry/TelemetryAPI.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index e5be70590f4..9d83152b24e 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -284,6 +284,27 @@ export default class TelemetryAPI { return value; } + hasNumericTelemetry(domainObject) { + if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) { + return false; + } + + let metadata = this.openmct.telemetry.getMetadata(domainObject); + + return metadata.values().length > 0 && this.#hasDomainAndNumericRange(metadata); + } + + #hasDomainAndNumericRange(metadata) { + const rangeValues = metadata.valuesForHints(['range']); + const domains = metadata.valuesForHints(['domain']); + + return ( + domains.length > 0 && + rangeValues.length > 0 && + !rangeValues.every((value) => value.format === 'string') + ); + } + /** * Generates a numeric hash value for an options object. The hash is consistent * for equivalent option objects regardless of property order. From 1326693643660d207db9df0d3e6f760f9ed7d389 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Fri, 20 Dec 2024 15:18:40 -0800 Subject: [PATCH 3/7] updated types api to return all types, updated annotations api to return annotatable types, cleaned up use of hasNumericTelemetry elsewhere in the code --- src/api/annotation/AnnotationAPI.js | 22 ++++++++++++++++++ src/api/types/TypeRegistry.js | 2 +- src/plugins/charts/scatter/plugin.js | 1 + src/plugins/gauge/GaugeCompositionPolicy.js | 20 +--------------- .../annotations/AnnotationsViewProvider.js | 8 +++++++ src/plugins/plot/PlotViewProvider.js | 23 +------------------ .../OverlayPlotCompositionPolicy.js | 23 ++++--------------- .../timeline/TimelineCompositionPolicy.js | 18 +-------------- 8 files changed, 39 insertions(+), 78 deletions(-) diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js index 3ca5889b1bb..f6da19d2b9d 100644 --- a/src/api/annotation/AnnotationAPI.js +++ b/src/api/annotation/AnnotationAPI.js @@ -95,6 +95,7 @@ export default class AnnotationAPI extends EventEmitter { this.availableTags = {}; this.namespaceToSaveAnnotations = ''; this.#targetComparatorMap = new Map(); + this.annotatableTypes = []; this.ANNOTATION_TYPES = ANNOTATION_TYPES; this.ANNOTATION_TYPE = ANNOTATION_TYPE; @@ -115,6 +116,17 @@ export default class AnnotationAPI extends EventEmitter { domainObject.annotationType = domainObject.annotationType || 'plotspatial'; } }); + + this.openmct.on('start', () => { + const types = this.openmct.types.getTypes(); + const typeKeys = Object.keys(types); + + typeKeys.forEach((key) => { + if (types[key].definition.annotatable) { + this.annotatableTypes.push(key); + } + }); + }); } /** * Creates an annotation on a given domain object (e.g., a plot) and a set of targets (e.g., telemetry objects) @@ -582,4 +594,14 @@ export default class AnnotationAPI extends EventEmitter { _.isEqual(targets, otherTargets) ); } + + /** + * Checks if the given type is annotatable + * @param {string} type The type to check + * @returns {boolean} Returns true if the type is annotatable + */ + isAnnotatableType(type) { + console.log('type', type, this.annotatableTypes); + return this.annotatableTypes.some((annotatableType) => annotatableType === type); + } } diff --git a/src/api/types/TypeRegistry.js b/src/api/types/TypeRegistry.js index 1dac938fb47..4ed36a2f1ad 100644 --- a/src/api/types/TypeRegistry.js +++ b/src/api/types/TypeRegistry.js @@ -94,7 +94,7 @@ export default class TypeRegistry { * @returns {Type[]} all registered types */ getTypes() { - return Object.values(this.types); + return this.types; } /** * Import legacy types. diff --git a/src/plugins/charts/scatter/plugin.js b/src/plugins/charts/scatter/plugin.js index 16198a250f1..b8a74f5c61f 100644 --- a/src/plugins/charts/scatter/plugin.js +++ b/src/plugins/charts/scatter/plugin.js @@ -40,6 +40,7 @@ export default function () { cssClass: 'icon-plot-scatter', description: 'View data as a scatter plot.', creatable: true, + annotatable: true, initialize: function (domainObject) { domainObject.composition = []; domainObject.configuration = { diff --git a/src/plugins/gauge/GaugeCompositionPolicy.js b/src/plugins/gauge/GaugeCompositionPolicy.js index d334c4f62c5..070cdc1dea3 100644 --- a/src/plugins/gauge/GaugeCompositionPolicy.js +++ b/src/plugins/gauge/GaugeCompositionPolicy.js @@ -21,28 +21,10 @@ *****************************************************************************/ export default function GaugeCompositionPolicy(openmct) { - function hasNumericTelemetry(domainObject) { - const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject); - if (!hasTelemetry) { - return false; - } - - const metadata = openmct.telemetry.getMetadata(domainObject); - - return metadata.values().length > 0 && hasDomainAndRange(metadata); - } - - function hasDomainAndRange(metadata) { - return ( - metadata.valuesForHints(['range']).length > 0 && - metadata.valuesForHints(['domain']).length > 0 - ); - } - return { allow: function (parent, child) { if (parent.type === 'gauge') { - return hasNumericTelemetry(child); + return openmct.telemetry.hasNumericTelemetry(child); } return true; diff --git a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js index bfc477d7c7b..970f3ee79ab 100644 --- a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js +++ b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js @@ -63,6 +63,14 @@ export default function AnnotationsViewProvider(openmct) { ); _destroy = destroy; }, + showTab: function () { + const isAnnotatableType = openmct.annotation.isAnnotatableType(domainObject.type); + const metadata = openmct.telemetry.getMetadata(domainObject); + const hasImagery = metadata?.valuesForHints(['image']).length > 0; + const hasNumericTelemetry = openmct.telemetry.hasNumericTelemetry(domainObject); + + return isAnnotatableType || hasImagery || hasNumericTelemetry; + }, priority: function () { return openmct.priority.DEFAULT; }, diff --git a/src/plugins/plot/PlotViewProvider.js b/src/plugins/plot/PlotViewProvider.js index 7645a1881e0..425e495aa9f 100644 --- a/src/plugins/plot/PlotViewProvider.js +++ b/src/plugins/plot/PlotViewProvider.js @@ -25,27 +25,6 @@ import mount from 'utils/mount'; import Plot from './PlotView.vue'; export default function PlotViewProvider(openmct) { - function hasNumericTelemetry(domainObject) { - if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) { - return false; - } - - let metadata = openmct.telemetry.getMetadata(domainObject); - - return metadata.values().length > 0 && hasDomainAndNumericRange(metadata); - } - - function hasDomainAndNumericRange(metadata) { - const rangeValues = metadata.valuesForHints(['range']); - const domains = metadata.valuesForHints(['domain']); - - return ( - domains.length > 0 && - rangeValues.length > 0 && - !rangeValues.every((value) => value.format === 'string') - ); - } - function isCompactView(objectPath) { let isChildOfTimeStrip = objectPath.find((object) => object.type === 'time-strip'); @@ -57,7 +36,7 @@ export default function PlotViewProvider(openmct) { name: 'Plot', cssClass: 'icon-telemetry', canView(domainObject, objectPath) { - return hasNumericTelemetry(domainObject); + return openmct.telemetry.hasNumericTelemetry(domainObject); }, view: function (domainObject, objectPath) { diff --git a/src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js b/src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js index ca5185e8b04..6178caebeec 100644 --- a/src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js +++ b/src/plugins/plot/overlayPlot/OverlayPlotCompositionPolicy.js @@ -1,25 +1,10 @@ export default function OverlayPlotCompositionPolicy(openmct) { - function hasNumericTelemetry(domainObject) { - const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject); - if (!hasTelemetry) { - return false; - } - - let metadata = openmct.telemetry.getMetadata(domainObject); - - return metadata.values().length > 0 && hasDomainAndRange(metadata); - } - - function hasDomainAndRange(metadata) { - return ( - metadata.valuesForHints(['range']).length > 0 && - metadata.valuesForHints(['domain']).length > 0 - ); - } - return { allow: function (parent, child) { - if (parent.type === 'telemetry.plot.overlay' && hasNumericTelemetry(child) === false) { + if ( + parent.type === 'telemetry.plot.overlay' && + !openmct.telemetry.hasNumericTelemetry(child) + ) { return false; } diff --git a/src/plugins/timeline/TimelineCompositionPolicy.js b/src/plugins/timeline/TimelineCompositionPolicy.js index 150f46217aa..c03b21467cd 100644 --- a/src/plugins/timeline/TimelineCompositionPolicy.js +++ b/src/plugins/timeline/TimelineCompositionPolicy.js @@ -23,22 +23,6 @@ const ALLOWED_TYPES = ['telemetry.plot.overlay', 'telemetry.plot.stacked', 'plan', 'gantt-chart']; const DISALLOWED_TYPES = ['telemetry.plot.bar-graph', 'telemetry.plot.scatter-plot']; export default function TimelineCompositionPolicy(openmct) { - function hasNumericTelemetry(domainObject, metadata) { - const hasTelemetry = openmct.telemetry.isTelemetryObject(domainObject); - if (!hasTelemetry || !metadata) { - return false; - } - - return metadata.values().length > 0 && hasDomainAndRange(metadata); - } - - function hasDomainAndRange(metadata) { - return ( - metadata.valuesForHints(['range']).length > 0 && - metadata.valuesForHints(['domain']).length > 0 - ); - } - function hasImageTelemetry(domainObject, metadata) { if (!metadata) { return false; @@ -54,7 +38,7 @@ export default function TimelineCompositionPolicy(openmct) { if ( !DISALLOWED_TYPES.includes(child.type) && - (hasNumericTelemetry(child, metadata) || + (openmct.telemetry.hasNumericTelemetry(child) || hasImageTelemetry(child, metadata) || ALLOWED_TYPES.includes(child.type)) ) { From cded8fc4d0e37c66f9abf75a8a4c8f31affba313 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Fri, 20 Dec 2024 15:21:47 -0800 Subject: [PATCH 4/7] not sure if scatter plots are annotatable --- src/plugins/charts/scatter/plugin.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/charts/scatter/plugin.js b/src/plugins/charts/scatter/plugin.js index b8a74f5c61f..16198a250f1 100644 --- a/src/plugins/charts/scatter/plugin.js +++ b/src/plugins/charts/scatter/plugin.js @@ -40,7 +40,6 @@ export default function () { cssClass: 'icon-plot-scatter', description: 'View data as a scatter plot.', creatable: true, - annotatable: true, initialize: function (domainObject) { domainObject.composition = []; domainObject.configuration = { From bac8c809756270a5ef859d9d94444afa3016a909 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 30 Dec 2024 15:19:10 -0800 Subject: [PATCH 5/7] added annotation tab conditions --- src/api/annotation/AnnotationAPI.js | 1 - .../inspectorViews/annotations/AnnotationsViewProvider.js | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js index f6da19d2b9d..b93ec0c0ff3 100644 --- a/src/api/annotation/AnnotationAPI.js +++ b/src/api/annotation/AnnotationAPI.js @@ -601,7 +601,6 @@ export default class AnnotationAPI extends EventEmitter { * @returns {boolean} Returns true if the type is annotatable */ isAnnotatableType(type) { - console.log('type', type, this.annotatableTypes); return this.annotatableTypes.some((annotatableType) => annotatableType === type); } } diff --git a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js index 970f3ee79ab..bbfc9b98b1b 100644 --- a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js +++ b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js @@ -40,7 +40,9 @@ export default function AnnotationsViewProvider(openmct) { view: function (selection) { let _destroy = null; - const domainObject = selection?.[0]?.[0]?.context?.item; + const selectionContext = selection?.[0]?.[0]?.context; + const domainObject = selectionContext?.item; + const isLayoutItem = selectionContext?.layoutItem; return { show: function (element) { @@ -64,6 +66,10 @@ export default function AnnotationsViewProvider(openmct) { _destroy = destroy; }, showTab: function () { + if (isLayoutItem) { + return false; + } + const isAnnotatableType = openmct.annotation.isAnnotatableType(domainObject.type); const metadata = openmct.telemetry.getMetadata(domainObject); const hasImagery = metadata?.valuesForHints(['image']).length > 0; From 3cba87d55baedec62bf5668674410998883693b4 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 30 Dec 2024 15:28:55 -0800 Subject: [PATCH 6/7] clean up the new hasNumericTelemetry method in the telemetry api --- src/api/telemetry/TelemetryAPI.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 9d83152b24e..122a38ec962 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -284,17 +284,23 @@ export default class TelemetryAPI { return value; } + /** + * Determines whether a domain object has numeric telemetry data. + * A domain object has numeric telemetry if it: + * 1. Has a telemetry property + * 2. Has telemetry metadata with domain values (like timestamps) + * 3. Has range values (measurements) where at least one is numeric + * + * @method hasNumericTelemetry + * @param {import('openmct').DomainObject} domainObject The domain object to check + * @returns {boolean} True if the object has numeric telemetry, false otherwise + */ hasNumericTelemetry(domainObject) { if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) { return false; } - let metadata = this.openmct.telemetry.getMetadata(domainObject); - - return metadata.values().length > 0 && this.#hasDomainAndNumericRange(metadata); - } - - #hasDomainAndNumericRange(metadata) { + const metadata = this.openmct.telemetry.getMetadata(domainObject); const rangeValues = metadata.valuesForHints(['range']); const domains = metadata.valuesForHints(['domain']); From cc99f19318e813cc85c89f0133c5688c8ef52bf8 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 30 Dec 2024 16:13:42 -0800 Subject: [PATCH 7/7] moving to canView, since that is where the logic should be --- .../annotations/AnnotationsViewProvider.js | 28 +++++++--------- .../styles/StylesInspectorViewProvider.js | 32 ++++++++----------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js index bbfc9b98b1b..50cb6381f42 100644 --- a/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js +++ b/src/plugins/inspectorViews/annotations/AnnotationsViewProvider.js @@ -30,19 +30,25 @@ export default function AnnotationsViewProvider(openmct) { name: 'Annotations', canView: function (selection) { const availableTags = openmct.annotation.getAvailableTags(); + const selectionContext = selection?.[0]?.[0]?.context; + const domainObject = selectionContext?.item; + const isLayoutItem = selectionContext?.layoutItem; - if (availableTags.length < 1) { + if (availableTags.length < 1 || isLayoutItem || !domainObject) { return false; } - return selection.length; + const isAnnotatableType = openmct.annotation.isAnnotatableType(domainObject.type); + const metadata = openmct.telemetry.getMetadata(domainObject); + const hasImagery = metadata?.valuesForHints(['image']).length > 0; + const hasNumericTelemetry = openmct.telemetry.hasNumericTelemetry(domainObject); + + return isAnnotatableType || hasImagery || hasNumericTelemetry; }, view: function (selection) { let _destroy = null; - const selectionContext = selection?.[0]?.[0]?.context; - const domainObject = selectionContext?.item; - const isLayoutItem = selectionContext?.layoutItem; + const domainObject = selection?.[0]?.[0]?.context?.item; return { show: function (element) { @@ -65,18 +71,6 @@ export default function AnnotationsViewProvider(openmct) { ); _destroy = destroy; }, - showTab: function () { - if (isLayoutItem) { - return false; - } - - const isAnnotatableType = openmct.annotation.isAnnotatableType(domainObject.type); - const metadata = openmct.telemetry.getMetadata(domainObject); - const hasImagery = metadata?.valuesForHints(['image']).length > 0; - const hasNumericTelemetry = openmct.telemetry.hasNumericTelemetry(domainObject); - - return isAnnotatableType || hasImagery || hasNumericTelemetry; - }, priority: function () { return openmct.priority.DEFAULT; }, diff --git a/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js b/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js index 8abc1acdc15..2984b34ac77 100644 --- a/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js +++ b/src/plugins/inspectorViews/styles/StylesInspectorViewProvider.js @@ -35,8 +35,8 @@ function isLayoutObject(selection, objectType) { ); } -function isCreatableObject(object, type) { - return NON_STYLABLE_TYPES.indexOf(object.type) < 0 && type.definition.creatable; +function isCreatableObject(object, typeObject) { + return NON_STYLABLE_TYPES.indexOf(object.type) < 0 && typeObject.definition.creatable; } export default function StylesInspectorViewProvider(openmct) { @@ -47,23 +47,28 @@ export default function StylesInspectorViewProvider(openmct) { canView: function (selection) { const objectSelection = selection?.[0]; const objectContext = objectSelection?.[0]?.context; - const layoutItem = objectContext?.layoutItem; const domainObject = objectContext?.item; - const isFlexibleLayoutContainer = - domainObject?.type === 'flexible-layout' && objectContext.type === 'container'; + const hasStyles = domainObject?.configuration?.objectStyles; + const isFlexibleLayoutContainer = ['flexible-layout', 'fixed-layout'].includes( + domainObject?.type + ); + const isLayoutItem = objectContext?.layoutItem; - if (layoutItem) { + if (isLayoutItem) { return true; } - if (!domainObject || isFlexibleLayoutContainer) { + if (!domainObject || isFlexibleLayoutContainer || !hasStyles) { return false; } - const type = openmct.types.get(domainObject.type); + const typeObject = openmct.types.get(domainObject.type); return ( - isLayoutObject(objectSelection, domainObject.type) || isCreatableObject(domainObject, type) + hasStyles || + isLayoutItem || + isLayoutObject(objectSelection, domainObject.type) || + isCreatableObject(domainObject, typeObject) ); }, view: function (selection) { @@ -91,15 +96,6 @@ export default function StylesInspectorViewProvider(openmct) { ); _destroy = destroy; }, - showTab: function () { - const objectSelection = selection?.[0]; - const objectContext = objectSelection?.[0]?.context; - const layoutItem = objectContext?.layoutItem; - const domainObject = objectContext?.item; - const hasStyles = domainObject?.configuration?.objectStyles; - - return layoutItem || hasStyles; - }, priority: function () { return openmct.priority.DEFAULT; },