diff --git a/src/plugins/timeline/TimelineCompositionPolicy.js b/src/plugins/timeline/TimelineCompositionPolicy.js new file mode 100644 index 0000000000..b922e04994 --- /dev/null +++ b/src/plugins/timeline/TimelineCompositionPolicy.js @@ -0,0 +1,70 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2021, 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. + *****************************************************************************/ +const ALLOWED_TYPES = [ + 'telemetry.plot.overlay', + 'telemetry.plot.stacked', + 'plan' +]; +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; + } + + return metadata.valuesForHints(['image']).length > 0; + } + + return { + allow: function (parent, child) { + if (parent.type === 'time-strip') { + const metadata = openmct.telemetry.getMetadata(child); + + if (!DISALLOWED_TYPES.includes(child.type) + && (hasNumericTelemetry(child, metadata) || hasImageTelemetry(child, metadata) || ALLOWED_TYPES.includes(child.type))) { + return true; + } + + return false; + } + + return true; + } + }; +} diff --git a/src/plugins/timeline/plugin.js b/src/plugins/timeline/plugin.js index 9ea701df0d..8e72d0e16b 100644 --- a/src/plugins/timeline/plugin.js +++ b/src/plugins/timeline/plugin.js @@ -22,6 +22,7 @@ import TimelineViewProvider from './TimelineViewProvider'; import timelineInterceptor from "./timelineInterceptor"; +import TimelineCompositionPolicy from "./TimelineCompositionPolicy"; export default function () { return function install(openmct) { @@ -39,6 +40,8 @@ export default function () { } }); timelineInterceptor(openmct); + openmct.composition.addPolicy(new TimelineCompositionPolicy(openmct).allow); + openmct.objectViews.addProvider(new TimelineViewProvider(openmct)); }; } diff --git a/src/plugins/timeline/pluginSpec.js b/src/plugins/timeline/pluginSpec.js index 2ef1a19f8f..0eb77e193c 100644 --- a/src/plugins/timeline/pluginSpec.js +++ b/src/plugins/timeline/pluginSpec.js @@ -62,6 +62,34 @@ describe('the plugin', function () { }) } }; + let timelineObject = { + "composition": [], + configuration: { + useIndependentTime: false, + timeOptions: { + mode: { + key: 'fixed' + }, + fixedOffsets: { + start: 10, + end: 11 + }, + clockOffsets: { + start: -(30 * 60 * 1000), + end: (30 * 60 * 1000) + } + } + }, + "name": "Some timestrip", + "type": "time-strip", + "location": "mine", + "modified": 1631005183584, + "persisted": 1631005183502, + "identifier": { + "namespace": "", + "key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9" + } + }; beforeEach((done) => { mockObjectPath = [ @@ -134,28 +162,7 @@ describe('the plugin', function () { beforeEach(() => { testViewObject = { - id: "test-object", - identifier: { - key: "test-object", - namespace: '' - }, - type: "time-strip", - configuration: { - useIndependentTime: false, - timeOptions: { - mode: { - key: 'fixed' - }, - fixedOffsets: { - start: 10, - end: 11 - }, - clockOffsets: { - start: -(30 * 60 * 1000), - end: (30 * 60 * 1000) - } - } - } + ...timelineObject }; const applicableViews = openmct.objectViews.get(testViewObject, mockObjectPath); @@ -187,15 +194,7 @@ describe('the plugin', function () { beforeEach(() => { timelineDomainObject = { - identifier: { - key: 'test-object', - namespace: '' - }, - type: 'time-strip', - id: "test-object", - configuration: { - useIndependentTime: false - }, + ...timelineObject, composition: [ { identifier: { @@ -236,27 +235,10 @@ describe('the plugin', function () { describe('the independent time conductor', () => { let timelineView; let testViewObject = { - id: "test-object", - identifier: { - key: "test-object", - namespace: '' - }, - type: "time-strip", + ...timelineObject, configuration: { - useIndependentTime: true, - timeOptions: { - mode: { - key: 'local' - }, - fixedOffsets: { - start: 10, - end: 11 - }, - clockOffsets: { - start: -(30 * 60 * 1000), - end: (30 * 60 * 1000) - } - } + ...timelineObject.configuration, + useIndependentTime: true } }; @@ -284,27 +266,15 @@ describe('the plugin', function () { describe('the independent time conductor - fixed', () => { let timelineView; let testViewObject2 = { + ...timelineObject, id: "test-object2", identifier: { key: "test-object2", namespace: '' }, - type: "time-strip", configuration: { - useIndependentTime: true, - timeOptions: { - mode: { - key: 'fixed' - }, - fixedOffsets: { - start: 10, - end: 11 - }, - clockOffsets: { - start: -(30 * 60 * 1000), - end: (30 * 60 * 1000) - } - } + ...timelineObject.configuration, + useIndependentTime: true } }; @@ -328,4 +298,68 @@ describe('the plugin', function () { }); }); + describe("The timestrip composition policy", () => { + let testObject; + beforeEach(() => { + testObject = { + ...timelineObject, + composition: [] + }; + }); + + it("allows composition for plots", () => { + const testTelemetryObject = { + identifier: { + namespace: "", + key: "test-object" + }, + type: "test-object", + name: "Test Object", + telemetry: { + values: [{ + key: "some-key", + name: "Some attribute", + hints: { + domain: 1 + } + }, { + key: "some-other-key", + name: "Another attribute", + hints: { + range: 1 + } + }] + } + }; + const composition = openmct.composition.get(testObject); + expect(() => { + composition.add(testTelemetryObject); + }).not.toThrow(); + expect(testObject.composition.length).toBe(1); + }); + + it("allows composition for plans", () => { + const composition = openmct.composition.get(testObject); + expect(() => { + composition.add(planObject); + }).not.toThrow(); + expect(testObject.composition.length).toBe(1); + }); + + it("disallows composition for non time-based plots", () => { + const barGraphObject = { + identifier: { + namespace: "", + key: "test-object" + }, + type: "telemetry.plot.bar-graph", + name: "Test Object" + }; + const composition = openmct.composition.get(testObject); + expect(() => { + composition.add(barGraphObject); + }).toThrow(); + expect(testObject.composition.length).toBe(0); + }); + }); });