diff --git a/.circleci/config.yml b/.circleci/config.yml index 5422dd9f63..7b6b4a6f05 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,9 +2,11 @@ version: 2.1 executors: pw-focal-development: docker: - - image: mcr.microsoft.com/playwright:v1.21.1-focal + - image: mcr.microsoft.com/playwright:v1.25.2-focal environment: NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed + PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps + PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742) parameters: BUST_CACHE: description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!" @@ -12,7 +14,7 @@ parameters: type: boolean commands: build_and_install: - description: "All steps used to build and install. Will not work on node10" + description: "All steps used to build and install. Will use cache if found" parameters: node-version: type: string @@ -23,7 +25,7 @@ commands: - node/install: install-npm: true node-version: << parameters.node-version >> - - run: npm install + - run: npm install --prefer-offline --no-audit --progress=false restore_cache_cmd: description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache" parameters: @@ -31,7 +33,7 @@ commands: type: string steps: - when: - condition: + condition: equal: [false, << pipeline.parameters.BUST_CACHE >> ] steps: - restore_cache: @@ -41,7 +43,7 @@ commands: parameters: node-version: type: string - steps: + steps: - save_cache: key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} paths: @@ -58,10 +60,14 @@ commands: ls -latR >> /tmp/artifacts/dir.txt - store_artifacts: path: /tmp/artifacts/ - upload_code_covio: - description: "Command to upload code coverage reports to codecov.io" - steps: - - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov + generate_e2e_code_cov_report: + description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test" + parameters: + suite: + type: string + steps: + - run: npm run cov:e2e:report || true + - run: npm run cov:e2e:<>:publish orbs: node: circleci/node@4.9.0 browser-tools: circleci/browser-tools@1.3.0 @@ -90,106 +96,120 @@ jobs: parameters: node-version: type: string - browser: - type: string executor: pw-focal-development steps: - build_and_install: node-version: <> - - when: - condition: - equal: [ "FirefoxESR", <> ] - steps: - - browser-tools/install-firefox: - version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/ - - when: - condition: - equal: [ "FirefoxHeadless", <> ] - steps: - - browser-tools/install-firefox - - when: - condition: - equal: [ "ChromeHeadless", <> ] - steps: - - browser-tools/install-chrome: - replace-existing: false - - run: npm run test -- --browsers=<> + - browser-tools/install-chrome: + replace-existing: false + - run: npm run test + - run: npm run cov:unit:publish - save_cache_cmd: node-version: <> - store_test_results: path: dist/reports/tests/ - store_artifacts: - path: dist/reports/ + path: coverage - generate_and_store_version_and_filesystem_artifacts e2e-test: parameters: node-version: type: string - suite: + suite: #stable or full + type: string + executor: pw-focal-development + parallelism: 4 + steps: + - build_and_install: + node-version: <> + - when: #Only install chrome-beta when running the 'full' suite to save $$$ + condition: + equal: [ "full", <> ] + steps: + - run: npx playwright install chrome-beta + - run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL} + - generate_e2e_code_cov_report: + suite: <> + - store_test_results: + path: test-results/results.xml + - store_artifacts: + path: test-results + - store_artifacts: + path: coverage + - store_artifacts: + path: html-test-results + - generate_and_store_version_and_filesystem_artifacts + perf-test: + parameters: + node-version: type: string executor: pw-focal-development steps: - build_and_install: node-version: <> - - run: npx playwright install - - run: npm run test:e2e:<> + - run: npm run test:perf - store_test_results: path: test-results/results.xml - store_artifacts: path: test-results + - store_artifacts: + path: html-test-results + - generate_and_store_version_and_filesystem_artifacts + visual-test: + parameters: + node-version: + type: string + executor: pw-focal-development + steps: + - build_and_install: + node-version: <> + - run: npm run test:e2e:visual + - store_test_results: + path: test-results/results.xml + - store_artifacts: + path: test-results + - store_artifacts: + path: html-test-results - generate_and_store_version_and_filesystem_artifacts workflows: overall-circleci-commit-status: #These jobs run on every commit jobs: - lint: - name: node16-lint - node-version: lts/gallium - - unit-test: - name: node14-chrome + name: node14-lint node-version: lts/fermium - browser: ChromeHeadless - post-steps: - - upload_code_covio - - unit-test: - name: node16-chrome - node-version: lts/gallium - browser: ChromeHeadless - unit-test: name: node18-chrome node-version: "18" - browser: ChromeHeadless - e2e-test: - name: e2e-ci + name: e2e-stable node-version: lts/gallium - suite: ci + suite: stable + - perf-test: + node-version: lts/gallium + - visual-test: + node-version: lts/gallium + the-nightly: #These jobs do not run on PRs, but against master at night jobs: - - unit-test: - name: node16-firefoxESR-nightly - node-version: lts/gallium - browser: FirefoxESR - - unit-test: - name: node14-firefox-nightly - node-version: lts/fermium - browser: FirefoxHeadless - unit-test: name: node14-chrome-nightly node-version: lts/fermium - browser: ChromeHeadless - unit-test: name: node16-chrome-nightly node-version: lts/gallium - browser: ChromeHeadless - unit-test: name: node18-chrome node-version: "18" - browser: ChromeHeadless - npm-audit: node-version: lts/gallium - e2e-test: name: e2e-full-nightly node-version: lts/gallium suite: full + - perf-test: + node-version: lts/gallium + - visual-test: + node-version: lts/gallium triggers: - schedule: cron: "0 0 * * *" diff --git a/.eslintrc.js b/.eslintrc.js index 319d7cf440..26e8074908 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,6 +29,7 @@ module.exports = { "you-dont-need-lodash-underscore/omit": "off", "you-dont-need-lodash-underscore/throttle": "off", "you-dont-need-lodash-underscore/flatten": "off", + "you-dont-need-lodash-underscore/get": "off", "no-bitwise": "error", "curly": "error", "eqeqeq": "error", diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5fe33cf1ba..2fd6b80917 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,7 +27,7 @@ assignees: '' #### Environment + * Open MCT Version: * Deployment Type: * OS: @@ -40,6 +40,8 @@ assignees: '' - [ ] Is there a workaround available? - [ ] Does this impact a critical component? - [ ] Is this just a visual bug with no functional impact? +- [ ] Does this block the execution of e2e tests? +- [ ] Does this have an impact on Performance? #### Additional Information diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 89fb12a9e3..a5525f4ce9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,10 +13,10 @@ Closes + + + diff --git a/src/plugins/charts/inspector/SeriesOptions.vue b/src/plugins/charts/bar/inspector/SeriesOptions.vue similarity index 89% rename from src/plugins/charts/inspector/SeriesOptions.vue rename to src/plugins/charts/bar/inspector/SeriesOptions.vue index 29c1f9cc3b..8bb8924461 100644 --- a/src/plugins/charts/inspector/SeriesOptions.vue +++ b/src/plugins/charts/bar/inspector/SeriesOptions.vue @@ -38,16 +38,19 @@
{{ name }}
- +
    +
  • + +
  • +
@@ -109,7 +112,6 @@ export default { } }, mounted() { - this.key = this.openmct.objects.makeKeyString(this.item); this.initColorAndName(); this.removeBarStylesListener = this.openmct.objects.observe(this.domainObject, `configuration.barStyles.series["${this.key}"]`, this.initColorAndName); }, @@ -120,6 +122,7 @@ export default { }, methods: { initColorAndName() { + this.key = this.openmct.objects.makeKeyString(this.item.identifier); // this is called before the plot is initialized if (!this.domainObject.configuration.barStyles.series[this.key]) { const color = this.colorPalette.getNextColor().asHexString(); diff --git a/src/plugins/charts/plugin.js b/src/plugins/charts/bar/plugin.js similarity index 89% rename from src/plugins/charts/plugin.js rename to src/plugins/charts/bar/plugin.js index 7a15d1cb6e..c0117b07c0 100644 --- a/src/plugins/charts/plugin.js +++ b/src/plugins/charts/bar/plugin.js @@ -28,14 +28,17 @@ export default function () { return function install(openmct) { openmct.types.addType(BAR_GRAPH_KEY, { key: BAR_GRAPH_KEY, - name: "Bar Graph", + name: "Graph", cssClass: "icon-bar-chart", - description: "View data as a bar graph. Can be added to Display Layouts.", + description: "Visualize data as a bar or line graph.", creatable: true, initialize: function (domainObject) { domainObject.composition = []; domainObject.configuration = { - barStyles: { series: {} } + barStyles: { series: {} }, + axes: {}, + useInterpolation: 'linear', + useBar: true }; }, priority: 891 diff --git a/src/plugins/charts/pluginSpec.js b/src/plugins/charts/bar/pluginSpec.js similarity index 78% rename from src/plugins/charts/pluginSpec.js rename to src/plugins/charts/bar/pluginSpec.js index 56e3577e2b..b63906bd50 100644 --- a/src/plugins/charts/pluginSpec.js +++ b/src/plugins/charts/bar/pluginSpec.js @@ -57,18 +57,18 @@ describe("the plugin", function () { const testTelemetry = [ { 'utc': 1, - 'some-key': 'some-value 1', - 'some-other-key': 'some-other-value 1' + 'some-key': ['1.3222'], + 'some-other-key': [1] }, { 'utc': 2, - 'some-key': 'some-value 2', - 'some-other-key': 'some-other-value 2' + 'some-key': ['2.555'], + 'some-other-key': [2] }, { 'utc': 3, - 'some-key': 'some-value 3', - 'some-other-key': 'some-other-value 3' + 'some-key': ['3.888'], + 'some-other-key': [3] } ]; @@ -123,7 +123,6 @@ describe("the plugin", function () { }); describe("The bar graph view", () => { - let testDomainObject; let barGraphObject; // eslint-disable-next-line no-unused-vars let component; @@ -135,51 +134,21 @@ describe("the plugin", function () { namespace: "", key: "test-plot" }, + configuration: { + barStyles: { + series: {} + }, + axes: {}, + useInterpolation: 'linear', + useBar: true + }, type: "telemetry.plot.bar-graph", name: "Test Bar Graph" }; - testDomainObject = { - identifier: { - namespace: "", - key: "test-object" - }, - configuration: { - barStyles: { - series: {} - } - }, - type: "test-object", - name: "Test Object", - telemetry: { - values: [{ - key: "utc", - format: "utc", - name: "Time", - hints: { - domain: 1 - } - }, { - key: "some-key", - name: "Some attribute", - hints: { - range: 1 - } - }, { - key: "some-other-key", - name: "Another attribute", - hints: { - range: 2 - } - }] - } - }; - mockComposition = new EventEmitter(); mockComposition.load = () => { - mockComposition.emit('add', testDomainObject); - - return [testDomainObject]; + return []; }; spyOn(openmct.composition, 'get').and.returnValue(mockComposition); @@ -247,15 +216,116 @@ describe("the plugin", function () { const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath); const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW); - const barGraphView = plotViewProvider.view(testDomainObject, [testDomainObject]); + const barGraphView = plotViewProvider.view(barGraphObject, [barGraphObject]); barGraphView.show(child, true); - expect(testDomainObject.configuration.barStyles.series["test-object"].name).toEqual("Test Object"); mockComposition.emit('add', dotFullTelemetryObject); - expect(testDomainObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object"); + expect(barGraphObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object"); barGraphView.destroy(); }); }); + describe("The spectral plot view for telemetry objects with array values", () => { + let barGraphObject; + // eslint-disable-next-line no-unused-vars + let component; + let mockComposition; + + beforeEach(async () => { + barGraphObject = { + identifier: { + namespace: "", + key: "test-plot" + }, + configuration: { + barStyles: { + series: {} + }, + axes: { + xKey: 'some-key', + yKey: 'some-other-key' + }, + useInterpolation: 'linear', + useBar: false + }, + type: "telemetry.plot.bar-graph", + name: "Test Bar Graph" + }; + + mockComposition = new EventEmitter(); + mockComposition.load = () => { + return []; + }; + + spyOn(openmct.composition, 'get').and.returnValue(mockComposition); + + let viewContainer = document.createElement("div"); + child.append(viewContainer); + component = new Vue({ + el: viewContainer, + components: { + BarGraph + }, + provide: { + openmct: openmct, + domainObject: barGraphObject, + composition: openmct.composition.get(barGraphObject) + }, + template: "" + }); + + await Vue.nextTick(); + }); + + it("Renders spectral plots", () => { + const dotFullTelemetryObject = { + identifier: { + namespace: "someNamespace", + key: "~OpenMCT~outer.test-object.foo.bar" + }, + type: "test-dotful-object", + name: "A Dotful Object", + telemetry: { + values: [{ + key: "utc", + format: "utc", + name: "Time", + hints: { + domain: 1 + } + }, { + key: "some-key", + name: "Some attribute", + formatString: '%0.2f[]', + hints: { + range: 1 + }, + source: 'some-key' + }, { + key: "some-other-key", + name: "Another attribute", + format: "number[]", + hints: { + range: 2 + }, + source: 'some-other-key' + }] + } + }; + + const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath); + const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW); + const barGraphView = plotViewProvider.view(barGraphObject, [barGraphObject]); + barGraphView.show(child, true); + mockComposition.emit('add', dotFullTelemetryObject); + + return Vue.nextTick().then(() => { + const plotElement = element.querySelector('.cartesianlayer .scatterlayer .trace .lines'); + expect(plotElement).not.toBeNull(); + barGraphView.destroy(); + }); + }); + }); + describe("the bar graph objects", () => { const mockObject = { name: 'A very nice bar graph', @@ -297,19 +367,26 @@ describe("the plugin", function () { 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 - } - }] + values: [ + { + key: "some-key", + source: "some-key", + name: "Some attribute", + format: "enum", + enumerations: [ + { + value: 0, + string: "OFF" + }, + { + value: 1, + string: "ON" + } + ], + hints: { + range: 1 + } + }] } }; const composition = openmct.composition.get(parent); @@ -412,7 +489,7 @@ describe("the plugin", function () { testDomainObject = { identifier: { namespace: "", - key: "test-object" + key: "~Some~foo.bar" }, type: "test-object", name: "Test Object", @@ -460,11 +537,16 @@ describe("the plugin", function () { isAlias: true } } - } + }, + axes: {}, + useInterpolation: 'linear', + useBar: true }, composition: [ { - key: '~Some~foo.bar' + identifier: { + key: '~Some~foo.bar' + } } ] } diff --git a/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js b/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js new file mode 100644 index 0000000000..710fb34028 --- /dev/null +++ b/src/plugins/charts/scatter/ScatterPlotCompositionPolicy.js @@ -0,0 +1,57 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +import { SCATTER_PLOT_KEY } from './scatterPlotConstants'; + +export default function ScatterPlotCompositionPolicy(openmct) { + function hasRange(metadata) { + const rangeValues = metadata.valuesForHints(['range']).map((value) => { + return value.source; + }); + + const uniqueRangeValues = new Set(rangeValues); + + return uniqueRangeValues && uniqueRangeValues.size > 1; + } + + function hasScatterPlotTelemetry(domainObject) { + if (!openmct.telemetry.isTelemetryObject(domainObject)) { + return false; + } + + let metadata = openmct.telemetry.getMetadata(domainObject); + + return metadata.values().length > 0 && hasRange(metadata); + } + + return { + allow: function (parent, child) { + if (parent.type === SCATTER_PLOT_KEY) { + if ((child.type === 'conditionSet') || (!hasScatterPlotTelemetry(child))) { + return false; + } + } + + return true; + } + }; +} diff --git a/src/plugins/charts/scatter/ScatterPlotForm.vue b/src/plugins/charts/scatter/ScatterPlotForm.vue new file mode 100644 index 0000000000..adef666dc0 --- /dev/null +++ b/src/plugins/charts/scatter/ScatterPlotForm.vue @@ -0,0 +1,146 @@ +/***************************************************************************** +* Open MCT, Copyright (c) 2014-2022, 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. +*****************************************************************************/ + + + + diff --git a/src/plugins/charts/scatter/ScatterPlotView.vue b/src/plugins/charts/scatter/ScatterPlotView.vue new file mode 100644 index 0000000000..129a3bca98 --- /dev/null +++ b/src/plugins/charts/scatter/ScatterPlotView.vue @@ -0,0 +1,351 @@ + + + + + diff --git a/src/plugins/charts/scatter/ScatterPlotViewProvider.js b/src/plugins/charts/scatter/ScatterPlotViewProvider.js new file mode 100644 index 0000000000..338d2eb3e3 --- /dev/null +++ b/src/plugins/charts/scatter/ScatterPlotViewProvider.js @@ -0,0 +1,79 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +import ScatterPlotView from './ScatterPlotView.vue'; +import { SCATTER_PLOT_KEY, SCATTER_PLOT_VIEW, TIME_STRIP_KEY } from './scatterPlotConstants.js'; +import Vue from 'vue'; + +export default function ScatterPlotViewProvider(openmct) { + function isCompactView(objectPath) { + let isChildOfTimeStrip = objectPath.find(object => object.type === TIME_STRIP_KEY); + + return isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath); + } + + return { + key: SCATTER_PLOT_VIEW, + name: 'Scatter Plot', + cssClass: 'icon-telemetry', + canView(domainObject, objectPath) { + return domainObject && domainObject.type === SCATTER_PLOT_KEY; + }, + + canEdit(domainObject, objectPath) { + return domainObject && domainObject.type === SCATTER_PLOT_KEY; + }, + + view: function (domainObject, objectPath) { + let component; + + return { + show: function (element) { + let isCompact = isCompactView(objectPath); + component = new Vue({ + el: element, + components: { + ScatterPlotView + }, + provide: { + openmct, + domainObject, + path: objectPath + }, + data() { + return { + options: { + compact: isCompact + } + }; + }, + template: '' + }); + }, + destroy: function () { + component.$destroy(); + component = undefined; + } + }; + } + }; +} diff --git a/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue new file mode 100644 index 0000000000..796a252ac7 --- /dev/null +++ b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue @@ -0,0 +1,393 @@ + + + diff --git a/src/plugins/charts/inspector/BarGraphOptions.vue b/src/plugins/charts/scatter/inspector/PlotOptions.vue similarity index 71% rename from src/plugins/charts/inspector/BarGraphOptions.vue rename to src/plugins/charts/scatter/inspector/PlotOptions.vue index a17fbc28bf..a72fcb8c9a 100644 --- a/src/plugins/charts/inspector/BarGraphOptions.vue +++ b/src/plugins/charts/scatter/inspector/PlotOptions.vue @@ -20,33 +20,28 @@ at runtime from the About dialog for additional information. --> diff --git a/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue b/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue new file mode 100644 index 0000000000..6781a27777 --- /dev/null +++ b/src/plugins/charts/scatter/inspector/PlotOptionsEdit.vue @@ -0,0 +1,262 @@ + + + diff --git a/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js b/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js new file mode 100644 index 0000000000..54487dfe37 --- /dev/null +++ b/src/plugins/charts/scatter/inspector/ScatterPlotInspectorViewProvider.js @@ -0,0 +1,48 @@ +import { SCATTER_PLOT_INSPECTOR_KEY, SCATTER_PLOT_KEY } from '../scatterPlotConstants'; +import Vue from 'vue'; +import PlotOptions from "./PlotOptions.vue"; + +export default function ScatterPlotInspectorViewProvider(openmct) { + return { + key: SCATTER_PLOT_INSPECTOR_KEY, + name: 'Bar Graph Inspector View', + canView: function (selection) { + if (selection.length === 0 || selection[0].length === 0) { + return false; + } + + let object = selection[0][0].context.item; + + return object + && object.type === SCATTER_PLOT_KEY; + }, + view: function (selection) { + let component; + + return { + show: function (element) { + component = new Vue({ + el: element, + components: { + PlotOptions + }, + provide: { + openmct, + domainObject: selection[0][0].context.item + }, + template: '' + }); + }, + destroy: function () { + if (component) { + component.$destroy(); + component = undefined; + } + } + }; + }, + priority: function () { + return 1; + } + }; +} diff --git a/src/plugins/charts/scatter/plugin.js b/src/plugins/charts/scatter/plugin.js new file mode 100644 index 0000000000..600c2970fd --- /dev/null +++ b/src/plugins/charts/scatter/plugin.js @@ -0,0 +1,127 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ +import { SCATTER_PLOT_KEY } from './scatterPlotConstants.js'; +import ScatterPlotViewProvider from './ScatterPlotViewProvider'; +import ScatterPlotInspectorViewProvider from './inspector/ScatterPlotInspectorViewProvider'; +import ScatterPlotCompositionPolicy from './ScatterPlotCompositionPolicy'; +import Vue from "vue"; +import ScatterPlotForm from "./ScatterPlotForm.vue"; + +export default function () { + return function install(openmct) { + openmct.forms.addNewFormControl('scatter-plot-form-control', getScatterPlotFormControl(openmct)); + + openmct.types.addType(SCATTER_PLOT_KEY, { + key: SCATTER_PLOT_KEY, + name: "Scatter Plot", + cssClass: "icon-plot-scatter", + description: "View data as a scatter plot.", + creatable: true, + initialize: function (domainObject) { + domainObject.composition = []; + domainObject.configuration = { + styles: {}, + axes: {}, + ranges: {} + }; + }, + form: [ + { + name: 'Underlay data (JSON file)', + key: 'selectFile', + control: 'file-input', + text: 'Select File...', + type: 'application/json', + removable: true, + hideFromInspector: true, + property: [ + "selectFile" + ] + }, + { + name: "Underlay ranges", + control: "scatter-plot-form-control", + cssClass: "l-input", + key: "scatterPlotForm", + required: false, + hideFromInspector: false, + property: [ + "configuration", + "ranges" + ], + validate: ({ value }, callback) => { + const { rangeMin, rangeMax, domainMin, domainMax } = value; + const valid = { + rangeMin, + rangeMax, + domainMin, + domainMax + }; + + if (callback) { + callback(valid); + } + + const values = Object.values(valid); + const hasAllValues = values.every(rangeValue => rangeValue !== undefined); + const hasNoValues = values.every(rangeValue => rangeValue === undefined); + + return hasAllValues || hasNoValues; + } + } + ], + priority: 891 + }); + + openmct.objectViews.addProvider(new ScatterPlotViewProvider(openmct)); + + openmct.inspectorViews.addProvider(new ScatterPlotInspectorViewProvider(openmct)); + + openmct.composition.addPolicy(new ScatterPlotCompositionPolicy(openmct).allow); + }; + + function getScatterPlotFormControl(openmct) { + return { + show(element, model, onChange) { + const rowComponent = new Vue({ + el: element, + components: { + ScatterPlotForm + }, + provide: { + openmct + }, + data() { + return { + model, + onChange + }; + }, + template: `` + }); + + return rowComponent; + } + }; + } +} + diff --git a/src/plugins/charts/scatter/pluginSpec.js b/src/plugins/charts/scatter/pluginSpec.js new file mode 100644 index 0000000000..2eb17c7a45 --- /dev/null +++ b/src/plugins/charts/scatter/pluginSpec.js @@ -0,0 +1,421 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import {createOpenMct, resetApplicationState} from "utils/testing"; +import Vue from "vue"; +import ScatterPlotPlugin from "./plugin"; +import ScatterPlot from './ScatterPlotView.vue'; +import EventEmitter from "EventEmitter"; +import { SCATTER_PLOT_VIEW, SCATTER_PLOT_KEY } from './scatterPlotConstants'; + +describe("the plugin", function () { + let element; + let child; + let openmct; + let telemetryPromise; + let telemetryPromiseResolve; + let mockObjectPath; + + beforeEach((done) => { + mockObjectPath = [ + { + name: 'mock folder', + type: 'fake-folder', + identifier: { + key: 'mock-folder', + namespace: '' + } + } + ]; + const testTelemetry = [ + { + 'utc': 1, + 'some-key': 'some-value 1', + 'some-other-key': 'some-other-value 1' + }, + { + 'utc': 2, + 'some-key': 'some-value 2', + 'some-other-key': 'some-other-value 2' + }, + { + 'utc': 3, + 'some-key': 'some-value 3', + 'some-other-key': 'some-other-value 3' + } + ]; + + openmct = createOpenMct(); + + telemetryPromise = new Promise((resolve) => { + telemetryPromiseResolve = resolve; + }); + + spyOn(openmct.telemetry, 'request').and.callFake(() => { + telemetryPromiseResolve(testTelemetry); + + return telemetryPromise; + }); + + openmct.install(new ScatterPlotPlugin()); + + element = document.createElement("div"); + element.style.width = "640px"; + element.style.height = "480px"; + child = document.createElement("div"); + child.style.width = "640px"; + child.style.height = "480px"; + element.appendChild(child); + document.body.appendChild(element); + + spyOn(window, 'ResizeObserver').and.returnValue({ + observe() {}, + unobserve() {}, + disconnect() {} + }); + + openmct.time.timeSystem("utc", { + start: 0, + end: 4 + }); + + openmct.types.addType("test-object", { + creatable: true + }); + + openmct.on("start", done); + openmct.startHeadless(); + }); + + afterEach((done) => { + openmct.time.timeSystem('utc', { + start: 0, + end: 1 + }); + resetApplicationState(openmct).then(done).catch(done); + }); + + describe("The scatter plot view", () => { + let testDomainObject; + let scatterPlotObject; + // eslint-disable-next-line no-unused-vars + let component; + let mockComposition; + + beforeEach(async () => { + scatterPlotObject = { + identifier: { + namespace: "", + key: "test-plot" + }, + type: "telemetry.plot.scatter-plot", + name: "Test Scatter Plot", + configuration: { + axes: {}, + styles: {} + } + }; + + testDomainObject = { + identifier: { + namespace: "", + key: "test-object" + }, + type: "test-object", + name: "Test Object", + telemetry: { + values: [{ + key: "utc", + format: "utc", + name: "Time", + hints: { + domain: 1 + } + }, { + key: "some-key", + name: "Some attribute", + hints: { + range: 1 + } + }, { + key: "some-other-key", + name: "Another attribute", + hints: { + range: 2 + } + }] + } + }; + + mockComposition = new EventEmitter(); + mockComposition.load = () => { + mockComposition.emit('add', testDomainObject); + + return [testDomainObject]; + }; + + spyOn(openmct.composition, 'get').and.returnValue(mockComposition); + + let viewContainer = document.createElement("div"); + child.append(viewContainer); + component = new Vue({ + el: viewContainer, + components: { + ScatterPlot + }, + provide: { + openmct: openmct, + domainObject: scatterPlotObject, + composition: openmct.composition.get(scatterPlotObject) + }, + template: "" + }); + + await Vue.nextTick(); + }); + + it("provides a scatter plot view", () => { + const applicableViews = openmct.objectViews.get(scatterPlotObject, mockObjectPath); + const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === SCATTER_PLOT_VIEW); + expect(plotViewProvider).toBeDefined(); + }); + + it("Renders plotly scatter plot", () => { + let scatterPlotElement = element.querySelectorAll(".plotly"); + expect(scatterPlotElement.length).toBe(1); + }); + }); + + describe("the scatter plot objects", () => { + const mockObject = { + name: 'A very nice scatter plot', + key: SCATTER_PLOT_KEY, + creatable: true + }; + + it('defines a scatter plot object type with the correct key', () => { + const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition; + expect(objectDef.key).toEqual(mockObject.key); + }); + + it('is creatable', () => { + const objectDef = openmct.types.get(SCATTER_PLOT_KEY).definition; + expect(objectDef.creatable).toEqual(mockObject.creatable); + }); + }); + + describe("The scatter plot composition policy", () => { + it("allows composition for telemetry that contain at least 2 ranges", () => { + const parent = { + "composition": [], + "configuration": { + axes: {}, + styles: {} + }, + "name": "Some Scatter Plot", + "type": "telemetry.plot.scatter-plot", + "location": "mine", + "modified": 1631005183584, + "persisted": 1631005183502, + "identifier": { + "namespace": "", + "key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9" + } + }; + 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 + } + }, { + key: "some-other-key2", + name: "Another attribute2", + hints: { + range: 2 + } + }] + } + }; + const composition = openmct.composition.get(parent); + expect(() => { + composition.add(testTelemetryObject); + }).not.toThrow(); + expect(parent.composition.length).toBe(1); + }); + + it("disallows composition for telemetry that don't contain at least 2 range hints", () => { + const parent = { + "composition": [], + "configuration": { + axes: {}, + styles: {} + }, + "name": "Some Scatter Plot", + "type": "telemetry.plot.scatter-plot", + "location": "mine", + "modified": 1631005183584, + "persisted": 1631005183502, + "identifier": { + "namespace": "", + "key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9" + } + }; + 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(parent); + expect(() => { + composition.add(testTelemetryObject); + }).toThrow(); + expect(parent.composition.length).toBe(0); + }); + }); + describe('the inspector view', () => { + let mockComposition; + let testDomainObject; + let selection; + let plotInspectorView; + let viewContainer; + let optionsElement; + beforeEach(async () => { + testDomainObject = { + identifier: { + namespace: "", + key: "test-object" + }, + type: "test-object", + name: "Test Object", + telemetry: { + values: [{ + key: "utc", + format: "utc", + name: "Time", + hints: { + domain: 1 + } + }, { + key: "some-key", + name: "Some attribute", + hints: { + range: 1 + } + }, { + key: "some-other-key", + name: "Another attribute", + hints: { + range: 2 + } + }] + } + }; + + selection = [ + [ + { + context: { + item: { + id: "test-object", + identifier: { + key: "test-object", + namespace: '' + }, + type: "telemetry.plot.scatter-plot", + configuration: { + axes: {}, + styles: { + } + }, + composition: [ + { + key: '~Some~foo.scatter' + } + ] + } + } + } + ] + ]; + + mockComposition = new EventEmitter(); + mockComposition.load = () => { + mockComposition.emit('add', testDomainObject); + + return [testDomainObject]; + }; + + spyOn(openmct.composition, 'get').and.returnValue(mockComposition); + + viewContainer = document.createElement('div'); + child.append(viewContainer); + + const applicableViews = openmct.inspectorViews.get(selection); + plotInspectorView = applicableViews[0]; + plotInspectorView.show(viewContainer); + + await Vue.nextTick(); + optionsElement = element.querySelector('.c-scatter-plot-options'); + }); + + afterEach(() => { + plotInspectorView.destroy(); + }); + + it('it renders the options', () => { + expect(optionsElement).toBeDefined(); + }); + }); +}); diff --git a/src/plugins/charts/scatter/scatterPlotConstants.js b/src/plugins/charts/scatter/scatterPlotConstants.js new file mode 100644 index 0000000000..e458be37c6 --- /dev/null +++ b/src/plugins/charts/scatter/scatterPlotConstants.js @@ -0,0 +1,4 @@ +export const SCATTER_PLOT_VIEW = 'scatter-plot.view'; +export const SCATTER_PLOT_KEY = 'telemetry.plot.scatter-plot'; +export const SCATTER_PLOT_INSPECTOR_KEY = 'telemetry.plot.scatter-plot.inspector'; +export const TIME_STRIP_KEY = 'time-strip'; diff --git a/src/plugins/clock/plugin.js b/src/plugins/clock/plugin.js index 72b8dadde2..0965985a7f 100644 --- a/src/plugins/clock/plugin.js +++ b/src/plugins/clock/plugin.js @@ -32,7 +32,7 @@ export default function ClockPlugin(options) { const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss'; openmct.types.addType('clock', { name: 'Clock', - description: 'A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.', + description: 'A digital clock that uses system time and supports a variety of display formats and timezones.', creatable: true, cssClass: 'icon-clock', initialize: function (domainObject) { @@ -89,6 +89,7 @@ export default function ClockPlugin(options) { "key": "timezone", "name": "Timezone", "control": "autocomplete", + "cssClass": "c-clock__timezone-selection c-menu--no-icon", "options": momentTimezone.tz.names(), property: [ 'configuration', diff --git a/src/plugins/condition/Condition.js b/src/plugins/condition/Condition.js index d3709d9671..9896e9746c 100644 --- a/src/plugins/condition/Condition.js +++ b/src/plugins/condition/Condition.js @@ -21,7 +21,7 @@ *****************************************************************************/ import EventEmitter from 'EventEmitter'; -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; import TelemetryCriterion from "./criterion/TelemetryCriterion"; import { evaluateResults } from './utils/evaluator'; import { getLatestTimestamp } from './utils/time'; diff --git a/src/plugins/condition/ConditionManager.js b/src/plugins/condition/ConditionManager.js index c0393351ae..d04ef8d3ca 100644 --- a/src/plugins/condition/ConditionManager.js +++ b/src/plugins/condition/ConditionManager.js @@ -22,7 +22,7 @@ import Condition from "./Condition"; import { getLatestTimestamp } from './utils/time'; -import uuid from "uuid"; +import { v4 as uuid } from 'uuid'; import EventEmitter from 'EventEmitter'; export default class ConditionManager extends EventEmitter { @@ -300,8 +300,11 @@ export default class ConditionManager extends EventEmitter { return this.compositionLoad.then(() => { let latestTimestamp; let conditionResults = {}; + let nextLegOptions = {...options}; + delete nextLegOptions.onPartialResponse; + const conditionRequests = this.conditions - .map(condition => condition.requestLADConditionResult(options)); + .map(condition => condition.requestLADConditionResult(nextLegOptions)); return Promise.all(conditionRequests) .then((results) => { diff --git a/src/plugins/condition/StyleRuleManager.js b/src/plugins/condition/StyleRuleManager.js index e7f201ca7e..18063b337c 100644 --- a/src/plugins/condition/StyleRuleManager.js +++ b/src/plugins/condition/StyleRuleManager.js @@ -78,11 +78,13 @@ export default class StyleRuleManager extends EventEmitter { this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => { this.openmct.telemetry.request(conditionSetDomainObject) .then(output => { - if (output && output.length) { + if (output && output.length && (this.conditionSetIdentifier && this.openmct.objects.areIdsEqual(conditionSetDomainObject.identifier, this.conditionSetIdentifier))) { this.handleConditionSetResultUpdated(output[0]); } }); - this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this)); + if (this.conditionSetIdentifier && this.openmct.objects.areIdsEqual(conditionSetDomainObject.identifier, this.conditionSetIdentifier)) { + this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this)); + } }); } diff --git a/src/plugins/condition/components/Condition.vue b/src/plugins/condition/components/Condition.vue index 791b371175..7201b6b31d 100644 --- a/src/plugins/condition/components/Condition.vue +++ b/src/plugins/condition/components/Condition.vue @@ -214,7 +214,7 @@ import Criterion from './Criterion.vue'; import ConditionDescription from "./ConditionDescription.vue"; import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants"; -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; export default { components: { diff --git a/src/plugins/condition/criterion/TelemetryCriterion.js b/src/plugins/condition/criterion/TelemetryCriterion.js index e343b9d598..75e91f7ddf 100644 --- a/src/plugins/condition/criterion/TelemetryCriterion.js +++ b/src/plugins/condition/criterion/TelemetryCriterion.js @@ -51,7 +51,11 @@ export default class TelemetryCriterion extends EventEmitter { } initialize() { - this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry); + this.telemetryObjectIdAsString = ""; + if (![undefined, null, ""].includes(this.telemetryDomainObjectDefinition?.telemetry)) { + this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry); + } + this.updateTelemetryObjects(this.telemetryDomainObjectDefinition.telemetryObjects); if (this.isValid() && this.isStalenessCheck() && this.isValidInput()) { this.subscribeForStaleData(); diff --git a/src/plugins/condition/plugin.js b/src/plugins/condition/plugin.js index 318cc4c5a5..df56d35dff 100644 --- a/src/plugins/condition/plugin.js +++ b/src/plugins/condition/plugin.js @@ -23,7 +23,7 @@ import ConditionSetViewProvider from './ConditionSetViewProvider.js'; import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy"; import ConditionSetMetadataProvider from './ConditionSetMetadataProvider'; import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider'; -import uuid from "uuid"; +import { v4 as uuid } from 'uuid'; export default function ConditionPlugin() { diff --git a/src/plugins/conditionWidget/components/ConditionWidget.vue b/src/plugins/conditionWidget/components/ConditionWidget.vue index 80220dd754..ef865b1c64 100644 --- a/src/plugins/conditionWidget/components/ConditionWidget.vue +++ b/src/plugins/conditionWidget/components/ConditionWidget.vue @@ -136,8 +136,8 @@ export default { this.url = url; } - const conditionSetIdentifier = domainObject.configuration.objectStyles.conditionSetIdentifier; - if (this.conditionSetIdentifier !== conditionSetIdentifier) { + const conditionSetIdentifier = domainObject.configuration?.objectStyles?.conditionSetIdentifier; + if (conditionSetIdentifier && this.conditionSetIdentifier !== conditionSetIdentifier) { this.conditionSetIdentifier = conditionSetIdentifier; } diff --git a/src/plugins/displayLayout/DisplayLayoutToolbar.js b/src/plugins/displayLayout/DisplayLayoutToolbar.js index 24038b8555..9bcd7facfb 100644 --- a/src/plugins/displayLayout/DisplayLayoutToolbar.js +++ b/src/plugins/displayLayout/DisplayLayoutToolbar.js @@ -93,7 +93,7 @@ define(['lodash'], function (_) { 'table': { value: 'table', name: 'Table', - class: 'icon-tabular-realtime' + class: 'icon-tabular-scrolling' } }; const APPLICABLE_VIEWS = { @@ -211,13 +211,15 @@ define(['lodash'], function (_) { options: [ { value: false, - icon: 'icon-frame-show', - title: "Frame visible" + icon: 'icon-frame-hide', + title: "Frame visible", + label: 'Hide frame' }, { value: true, - icon: 'icon-frame-hide', - title: "Frame hidden" + icon: 'icon-frame-show', + title: "Frame hidden", + label: 'Show frame' } ] }; @@ -401,6 +403,7 @@ define(['lodash'], function (_) { }, icon: "icon-pencil", title: "Edit text properties", + label: "Edit text", dialog: DIALOG_FORM.text }; } @@ -514,12 +517,14 @@ define(['lodash'], function (_) { { value: true, icon: 'icon-eye-open', - title: "Show units" + title: "Show units", + label: "Show units" }, { value: false, icon: 'icon-eye-disabled', - title: "Hide units" + title: "Hide units", + label: "Hide units" } ] }; @@ -562,6 +567,7 @@ define(['lodash'], function (_) { domainObject: selectedParent, icon: "icon-object", title: "Switch the way this telemetry is displayed", + label: "View type", options: viewOptions, method: function (option) { displayLayoutContext.switchViewType(selectedItemContext, option.value, selection); @@ -662,9 +668,9 @@ define(['lodash'], function (_) { 'display-mode': [], 'telemetry-value': [], 'style': [], + 'unit-toggle': [], 'position': [], 'duplicate': [], - 'unit-toggle': [], 'remove': [], 'toggle-grid': [] }; @@ -689,6 +695,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -712,9 +719,17 @@ define(['lodash'], function (_) { toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)]; } + if (toolbar['unit-toggle'].length === 0) { + let toggleUnitsButton = getToggleUnitsButton(selectedParent, selectedObjects); + if (toggleUnitsButton) { + toolbar['unit-toggle'] = [toggleUnitsButton]; + } + } + if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -729,17 +744,11 @@ define(['lodash'], function (_) { if (toolbar.viewSwitcher.length === 0) { toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)]; } - - if (toolbar['unit-toggle'].length === 0) { - let toggleUnitsButton = getToggleUnitsButton(selectedParent, selectedObjects); - if (toggleUnitsButton) { - toolbar['unit-toggle'] = [toggleUnitsButton]; - } - } } else if (layoutItem.type === 'text-view') { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -758,6 +767,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -772,6 +782,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -786,6 +797,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getX2Input(selectedParent, selectedObjects), diff --git a/src/plugins/displayLayout/DrawingObjectTypes.js b/src/plugins/displayLayout/DrawingObjectTypes.js new file mode 100644 index 0000000000..9b34e808c9 --- /dev/null +++ b/src/plugins/displayLayout/DrawingObjectTypes.js @@ -0,0 +1,34 @@ +const displayLayoutDrawingObjectTypes = { + 'box-view': { + name: "Box", + creatable: false, + description: 'A rectangle shape.', + cssClass: 'icon-box-round-corners' + }, + 'ellipse-view': { + name: "Ellipse", + creatable: false, + description: 'A ellipse shape.', + cssClass: 'icon-circle' + }, + 'line-view': { + name: "Line", + creatable: false, + description: 'A line.', + cssClass: 'icon-line-horz' + }, + 'text-view': { + name: "Text", + creatable: false, + description: 'An editable text box.', + cssClass: 'icon-font' + }, + 'image-view': { + name: "Image", + creatable: false, + description: 'An image.', + cssClass: 'icon-image' + } +}; + +export default displayLayoutDrawingObjectTypes; diff --git a/src/plugins/displayLayout/components/DisplayLayout.vue b/src/plugins/displayLayout/components/DisplayLayout.vue index d260af3dc6..98afcba651 100644 --- a/src/plugins/displayLayout/components/DisplayLayout.vue +++ b/src/plugins/displayLayout/components/DisplayLayout.vue @@ -73,7 +73,7 @@ diff --git a/src/plugins/faultManagement/FaultManagementInspectorViewProvider.js b/src/plugins/faultManagement/FaultManagementInspectorViewProvider.js new file mode 100644 index 0000000000..b4500496fc --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementInspectorViewProvider.js @@ -0,0 +1,71 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import FaultManagementInspector from './FaultManagementInspector.vue'; + +import Vue from 'vue'; + +import { FAULT_MANAGEMENT_INSPECTOR, FAULT_MANAGEMENT_TYPE } from './constants'; + +export default function FaultManagementInspectorViewProvider(openmct) { + return { + openmct: openmct, + key: FAULT_MANAGEMENT_INSPECTOR, + name: 'FAULT_MANAGEMENT_TYPE', + canView: (selection) => { + if (selection.length !== 1 || selection[0].length === 0) { + return false; + } + + let object = selection[0][0].context.item; + + return object && object.type === FAULT_MANAGEMENT_TYPE; + }, + view: (selection) => { + let component; + + return { + show: function (element) { + component = new Vue({ + el: element, + components: { + FaultManagementInspector + }, + provide: { + openmct + }, + template: '' + }); + }, + destroy: function () { + if (component) { + component.$destroy(); + component = undefined; + } + } + }; + }, + priority: () => { + return 1; + } + }; +} diff --git a/src/plugins/faultManagement/FaultManagementListHeader.vue b/src/plugins/faultManagement/FaultManagementListHeader.vue new file mode 100644 index 0000000000..11c3bd6286 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementListHeader.vue @@ -0,0 +1,105 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + + diff --git a/src/plugins/faultManagement/FaultManagementListItem.vue b/src/plugins/faultManagement/FaultManagementListItem.vue new file mode 100644 index 0000000000..2a2f6bf858 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementListItem.vue @@ -0,0 +1,223 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + diff --git a/src/plugins/faultManagement/FaultManagementListView.vue b/src/plugins/faultManagement/FaultManagementListView.vue new file mode 100644 index 0000000000..be19cbfe50 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementListView.vue @@ -0,0 +1,301 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + + diff --git a/src/plugins/faultManagement/FaultManagementObjectProvider.js b/src/plugins/faultManagement/FaultManagementObjectProvider.js new file mode 100644 index 0000000000..9565c27c1f --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementObjectProvider.js @@ -0,0 +1,56 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import { FAULT_MANAGEMENT_TYPE, FAULT_MANAGEMENT_VIEW, FAULT_MANAGEMENT_NAMESPACE } from './constants'; + +export default class FaultManagementObjectProvider { + constructor(openmct) { + this.openmct = openmct; + this.namespace = FAULT_MANAGEMENT_NAMESPACE; + this.key = FAULT_MANAGEMENT_VIEW; + this.objects = {}; + + this.createFaultManagementRootObject(); + } + + createFaultManagementRootObject() { + this.rootObject = { + identifier: { + key: this.key, + namespace: this.namespace + }, + name: 'Fault Management', + type: FAULT_MANAGEMENT_TYPE, + location: 'ROOT' + }; + + this.openmct.objects.addRoot(this.rootObject.identifier); + } + + get(identifier) { + if (identifier.key === FAULT_MANAGEMENT_VIEW) { + return Promise.resolve(this.rootObject); + } + + return Promise.reject(); + } +} diff --git a/src/plugins/faultManagement/FaultManagementPlugin.js b/src/plugins/faultManagement/FaultManagementPlugin.js new file mode 100644 index 0000000000..93dda8f5b7 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementPlugin.js @@ -0,0 +1,42 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import FaultManagementViewProvider from './FaultManagementViewProvider'; +import FaultManagementObjectProvider from './FaultManagementObjectProvider'; +import FaultManagementInspectorViewProvider from './FaultManagementInspectorViewProvider'; + +import { FAULT_MANAGEMENT_TYPE, FAULT_MANAGEMENT_NAMESPACE } from './constants'; + +export default function FaultManagementPlugin() { + return function (openmct) { + openmct.types.addType(FAULT_MANAGEMENT_TYPE, { + name: 'Fault Management', + creatable: false, + description: 'Fault Management View', + cssClass: 'icon-bell' + }); + + openmct.objectViews.addProvider(new FaultManagementViewProvider(openmct)); + openmct.inspectorViews.addProvider(new FaultManagementInspectorViewProvider(openmct)); + openmct.objects.addProvider(FAULT_MANAGEMENT_NAMESPACE, new FaultManagementObjectProvider(openmct)); + }; +} diff --git a/src/plugins/faultManagement/FaultManagementSearch.vue b/src/plugins/faultManagement/FaultManagementSearch.vue new file mode 100644 index 0000000000..bfd2060ff1 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementSearch.vue @@ -0,0 +1,90 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + + diff --git a/src/plugins/faultManagement/FaultManagementToolbar.vue b/src/plugins/faultManagement/FaultManagementToolbar.vue new file mode 100644 index 0000000000..6134a449bd --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementToolbar.vue @@ -0,0 +1,102 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + + diff --git a/src/plugins/faultManagement/FaultManagementView.vue b/src/plugins/faultManagement/FaultManagementView.vue new file mode 100644 index 0000000000..71ba7cfe73 --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementView.vue @@ -0,0 +1,76 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + + + + diff --git a/src/plugins/faultManagement/FaultManagementViewProvider.js b/src/plugins/faultManagement/FaultManagementViewProvider.js new file mode 100644 index 0000000000..9576cfd97c --- /dev/null +++ b/src/plugins/faultManagement/FaultManagementViewProvider.js @@ -0,0 +1,69 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import FaultManagementView from './FaultManagementView.vue'; +import { FAULT_MANAGEMENT_TYPE, FAULT_MANAGEMENT_VIEW } from './constants'; +import Vue from 'vue'; + +export default class FaultManagementViewProvider { + constructor(openmct) { + this.openmct = openmct; + this.key = FAULT_MANAGEMENT_VIEW; + } + + canView(domainObject) { + return domainObject.type === FAULT_MANAGEMENT_TYPE; + } + + canEdit(domainObject) { + return false; + } + + view(domainObject) { + let component; + const openmct = this.openmct; + + return { + show: (element) => { + component = new Vue({ + el: element, + components: { + FaultManagementView + }, + provide: { + openmct, + domainObject + }, + template: '' + }); + }, + destroy: () => { + if (!component) { + return; + } + + component.$destroy(); + component = undefined; + } + }; + } +} diff --git a/src/plugins/faultManagement/constants.js b/src/plugins/faultManagement/constants.js new file mode 100644 index 0000000000..9f0be44a51 --- /dev/null +++ b/src/plugins/faultManagement/constants.js @@ -0,0 +1,122 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 FAULT_SEVERITY = { + 'CRITICAL': { + name: 'CRITICAL', + value: 'critical', + priority: 0 + }, + 'WARNING': { + name: 'WARNING', + value: 'warning', + priority: 1 + }, + 'WATCH': { + name: 'WATCH', + value: 'watch', + priority: 2 + } +}; + +export const FAULT_MANAGEMENT_TYPE = 'faultManagement'; +export const FAULT_MANAGEMENT_INSPECTOR = 'faultManagementInspector'; +export const FAULT_MANAGEMENT_ALARMS = 'alarms'; +export const FAULT_MANAGEMENT_GLOBAL_ALARMS = 'global-alarm-status'; +export const FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS = [ + { + name: '5 Minutes', + value: 300000 + }, + { + name: '10 Minutes', + value: 600000 + }, + { + name: '15 Minutes', + value: 900000 + }, + { + name: 'Indefinite', + value: 0 + } +]; +export const FAULT_MANAGEMENT_VIEW = 'faultManagement.view'; +export const FAULT_MANAGEMENT_NAMESPACE = 'faults.taxonomy'; +export const FILTER_ITEMS = [ + 'Standard View', + 'Acknowledged', + 'Unacknowledged', + 'Shelved' +]; +export const SORT_ITEMS = { + 'newest-first': { + name: 'Newest First', + value: 'newest-first', + sortFunction: (a, b) => { + if (b.triggerTime > a.triggerTime) { + return 1; + } + + if (a.triggerTime > b.triggerTime) { + return -1; + } + + return 0; + } + }, + 'oldest-first': { + name: 'Oldest First', + value: 'oldest-first', + sortFunction: (a, b) => { + if (a.triggerTime > b.triggerTime) { + return 1; + } + + if (a.triggerTime < b.triggerTime) { + return -1; + } + + return 0; + } + }, + 'severity': { + name: 'Severity', + value: 'severity', + sortFunction: (a, b) => { + const diff = FAULT_SEVERITY[a.severity].priority - FAULT_SEVERITY[b.severity].priority; + if (diff !== 0) { + return diff; + } + + if (b.triggerTime > a.triggerTime) { + return 1; + } + + if (a.triggerTime > b.triggerTime) { + return -1; + } + + return 0; + } + } +}; diff --git a/src/plugins/faultManagement/fault-manager.scss b/src/plugins/faultManagement/fault-manager.scss new file mode 100644 index 0000000000..e1c97443de --- /dev/null +++ b/src/plugins/faultManagement/fault-manager.scss @@ -0,0 +1,268 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ +$colorFaultItemFg: $colorBodyFg; +$colorFaultItemFgEmphasis: $colorBodyFgEm; +$colorFaultItemBg: pullForward($colorBodyBg, 5%); + +/*********************************************** SEARCH */ +.c-fault-mgmt__search-row { + display: flex; + align-items: center; + flex: 0 0 auto; + > * + * { + margin-left: 10px; + float: right; + } +} + +.c-fault-mgmt-search { + width: 95%; +} + +/*********************************************** TOOLBAR */ +.c-fault-mgmt__toolbar { + display: flex; + justify-content: center; + flex: 0 0 auto; + > * + * { + margin-left: $interiorMargin; + } +} + +/*********************************************** LIST VIEW */ +.c-faults-list-view { + display: flex; + flex-direction: column; + height: 100%; + + > * + * { + margin-top: $interiorMargin; + } +} + +.c-faults-list-view-header-item-container { + display: grid; + width: 100%; + grid-template-columns: max-content max-content repeat(5,minmax(max-content, 20%)) max-content; + grid-row-gap: $interiorMargin; + + &-wrapper { + flex: 1 1 auto; + padding-right: $interiorMargin; // Fend of from scrollbar + overflow-y: auto; + } + + .--width-less-than-600 & { + grid-template-columns: max-content max-content 1fr 1fr max-content; + } +} + +.c-faults-list-view-item-body { + display: contents; +} + +/*********************************************** LIST */ +.c-fault-mgmt__list { + display: contents; + color: $colorFaultItemFg; + + &-checkbox{ + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + + &-severity { + font-size: 2em; + + &.is-severity-critical { + @include glyphBefore($glyph-icon-alert-triangle); + color: $colorStatusError; + } + + &.is-severity-warning { + @include glyphBefore($glyph-icon-alert-rect); + color: $colorStatusAlert; + } + + &.is-severity-watch { + @include glyphBefore($glyph-icon-info); + color: $colorCommand; + } + } + + &-content { + display: contents; + + .--width-less-than-600 & { + display: flex; + flex-wrap: wrap; + grid-column: span 2; + } + } + + &-pathname { + padding-right: $interiorMarginLg; + overflow-wrap: anywhere; + min-width: 100px; + + } + &-path { + font-size: .85em; + margin-left: $interiorMargin; + } + + &-faultname{ + font-size: 1.3em; + margin-left: $interiorMargin; + } + + &-content-right { + display: contents; + } + + &-trigTime { + grid-column: 6 / span 2; + } + + &-action-wrapper { + text-align: right; + flex: 0 0 auto; + align-items: stretch; + } + + &-action-button { + flex: 0 0 auto; + margin-left: auto; + text-align: right; + } + + // STATES + &.is-unacknowledged { + color: $colorFaultItemFgEmphasis; + .c-fault-mgmt__list-severity { + @include pulse($animName: severityAnim, $dur: 200ms); + } + } + + &.is-acknowledged, + &.is-shelved { + .c-fault-mgmt__list-severity { + &:before { + opacity: 60%; + //font-size: 1.5em; + } + + &:after { + color: $colorFaultItemFgEmphasis; + display: block; + font-family: symbolsfont; + position: absolute; + //text-shadow: black 0 0 2px; + right: -3px; + bottom: -3px; + transform-origin: right bottom; + transform: scale(0.6); + } + } + } + + &.is-shelved { + .c-fault-mgmt__list-pathname { + font-style: italic; + } + } + + &.is-acknowledged .c-fault-mgmt__list-severity:after { + content: $glyph-icon-check; + } + + &.is-shelved .c-fault-mgmt__list-severity:after { + content: $glyph-icon-timer; + } +} + +/*********************************************** LIST HEADER */ +.c-fault-mgmt__list-header { + display: contents; + border-radius: $controlCr; + align-items: center; + + * { + margin: 0px; + border-radius: 0px; + } + + .--width-less-than-600 & { + .c-fault-mgmt__list-content-right { + display:none; + } + } + + &-content { + display: contents; + } + + &-results { + grid-column: 2 / span 2; + font-size: 1em; + height: auto; + } + + &-action-wrapper { + grid-column: 7 / span 2; + + .--width-less-than-600 & { + grid-column: 4 / span 2; + } + } +} + +/*********************************************** GRID ITEM */ +.c-fault-mgmt-item { + $p: $interiorMargin; + padding: $p; + background: $colorFaultItemBg; + white-space: nowrap; + + &-header { + $c: $colorBodyBg; + background: $c; + border-bottom: 5px solid $c; // Creates illusion of "space" beneath header + min-height: 30px; // Needed to align cells + padding: $p; + position: sticky; + top: 0; + z-index: 2; + } + + &__value { + @include isLimit(); + background: rgba($colorBodyFg, 0.1); + padding: $p; + border-radius: $controlCr; + display: inline-flex; + } + + .is-selected & { + background: $colorSelectedBg; + } +} diff --git a/src/plugins/faultManagement/pluginSpec.js b/src/plugins/faultManagement/pluginSpec.js new file mode 100644 index 0000000000..29169c05c7 --- /dev/null +++ b/src/plugins/faultManagement/pluginSpec.js @@ -0,0 +1,103 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +import { + createOpenMct, + resetApplicationState +} from '../../utils/testing'; +import { + FAULT_MANAGEMENT_TYPE, + FAULT_MANAGEMENT_VIEW, + FAULT_MANAGEMENT_NAMESPACE +} from './constants'; + +describe("The Fault Management Plugin", () => { + let openmct; + const faultDomainObject = { + name: 'it is not your fault', + type: FAULT_MANAGEMENT_TYPE, + identifier: { + key: 'nobodies', + namespace: 'fault' + } + }; + + beforeEach(() => { + openmct = createOpenMct(); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + it('is not installed by default', () => { + const typeDef = openmct.types.get(FAULT_MANAGEMENT_TYPE).definition; + + expect(typeDef.name).toBe('Unknown Type'); + }); + + it('can be installed', () => { + openmct.install(openmct.plugins.FaultManagement()); + const typeDef = openmct.types.get(FAULT_MANAGEMENT_TYPE).definition; + + expect(typeDef.name).toBe('Fault Management'); + }); + + describe('once it is installed', () => { + beforeEach(() => { + openmct.install(openmct.plugins.FaultManagement()); + }); + + it('provides a view for fault management types', () => { + const applicableViews = openmct.objectViews.get(faultDomainObject, []); + const faultManagementView = applicableViews.find( + (viewProvider) => viewProvider.key === FAULT_MANAGEMENT_VIEW + ); + + expect(applicableViews.length).toEqual(1); + expect(faultManagementView).toBeDefined(); + }); + + it('provides an inspector view for fault management types', () => { + const faultDomainObjectSelection = [[ + { + context: { + item: faultDomainObject + } + } + ]]; + const applicableInspectorViews = openmct.inspectorViews.get(faultDomainObjectSelection); + + expect(applicableInspectorViews.length).toEqual(1); + }); + + it('creates a root object for fault management', async () => { + const root = await openmct.objects.getRoot(); + const rootCompositionCollection = openmct.composition.get(root); + const rootComposition = await rootCompositionCollection.load(); + const faultObject = rootComposition.find(obj => obj.identifier.namespace === FAULT_MANAGEMENT_NAMESPACE); + + expect(faultObject).toBeDefined(); + }); + + }); +}); diff --git a/src/plugins/flexibleLayout/components/flexible-layout.scss b/src/plugins/flexibleLayout/components/flexible-layout.scss index ac44eb3d3c..6fe96a446b 100644 --- a/src/plugins/flexibleLayout/components/flexible-layout.scss +++ b/src/plugins/flexibleLayout/components/flexible-layout.scss @@ -141,6 +141,10 @@ } } } + + [s-selected].c-fl-frame__drag-wrapper { + border: $editFrameSelectedBorder; + } } /****** THEIR FRAMES */ diff --git a/src/plugins/flexibleLayout/components/flexibleLayout.vue b/src/plugins/flexibleLayout/components/flexibleLayout.vue index 503db9a1fc..c2f8958dee 100644 --- a/src/plugins/flexibleLayout/components/flexibleLayout.vue +++ b/src/plugins/flexibleLayout/components/flexibleLayout.vue @@ -281,6 +281,10 @@ export default { return false; } + if (!this.isEditing) { + return false; + } + let containerId = event.dataTransfer.getData('containerid'); let container = this.containers.filter((c) => c.id === containerId)[0]; let containerPos = this.containers.indexOf(container); diff --git a/src/plugins/flexibleLayout/components/frame.vue b/src/plugins/flexibleLayout/components/frame.vue index 70e6802a63..8515e718b4 100644 --- a/src/plugins/flexibleLayout/components/frame.vue +++ b/src/plugins/flexibleLayout/components/frame.vue @@ -31,7 +31,7 @@
{ + this.domainObjectPromise.then((object) => { this.setDomainObject(object); }); } @@ -112,7 +114,13 @@ export default { this.dragGhost = document.getElementById('js-fl-drag-ghost'); }, beforeDestroy() { - if (this.domainObject.isMutable) { + if (this.domainObjectPromise) { + this.domainObjectPromise.then(() => { + if (this?.domainObject?.isMutable) { + this.openmct.objects.destroyMutable(this.domainObject); + } + }); + } else if (this?.domainObject?.isMutable) { this.openmct.objects.destroyMutable(this.domainObject); } diff --git a/src/plugins/flexibleLayout/pluginSpec.js b/src/plugins/flexibleLayout/pluginSpec.js index c88c7ffea6..470fb6e8b3 100644 --- a/src/plugins/flexibleLayout/pluginSpec.js +++ b/src/plugins/flexibleLayout/pluginSpec.js @@ -22,6 +22,7 @@ import { createOpenMct, resetApplicationState } from 'utils/testing'; import FlexibleLayout from './plugin'; +import Vue from 'vue'; describe('the plugin', function () { let element; @@ -61,7 +62,7 @@ describe('the plugin', function () { element.appendChild(child); openmct.on('start', done); - openmct.startHeadless(); + openmct.start(child); }); afterEach(() => { @@ -83,6 +84,16 @@ describe('the plugin', function () { it('provides a view', () => { expect(flexibleLayoutViewProvider).toBeDefined(); }); + + it('renders a view', async () => { + const flexibleView = flexibleLayoutViewProvider.view(testViewObject, []); + flexibleView.show(child, false); + + await Vue.nextTick(); + const flexTitle = child.querySelector('.l-browse-bar .c-object-label__name'); + + expect(flexTitle).not.toBeNull(); + }); }); describe('the toolbar', () => { diff --git a/src/plugins/flexibleLayout/toolbarProvider.js b/src/plugins/flexibleLayout/toolbarProvider.js index 49fdff416f..5d6663f667 100644 --- a/src/plugins/flexibleLayout/toolbarProvider.js +++ b/src/plugins/flexibleLayout/toolbarProvider.js @@ -159,7 +159,7 @@ function ToolbarProvider(openmct) { let prompt = openmct.overlays.dialog({ iconClass: 'alert', - message: 'This action will permanently delete this container from this Flexible Layout', + message: 'This action will permanently delete this container from this Flexible Layout. Do you want to continue?', buttons: [ { label: 'OK', diff --git a/src/plugins/flexibleLayout/utils/container.js b/src/plugins/flexibleLayout/utils/container.js index 9ee51b2255..a26bf08add 100644 --- a/src/plugins/flexibleLayout/utils/container.js +++ b/src/plugins/flexibleLayout/utils/container.js @@ -1,4 +1,4 @@ -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; class Container { constructor(size) { diff --git a/src/plugins/flexibleLayout/utils/frame.js b/src/plugins/flexibleLayout/utils/frame.js index a444a20eab..767464419e 100644 --- a/src/plugins/flexibleLayout/utils/frame.js +++ b/src/plugins/flexibleLayout/utils/frame.js @@ -1,4 +1,4 @@ -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; class Frame { constructor(domainObjectIdentifier, size) { diff --git a/src/plugins/formActions/CreateAction.js b/src/plugins/formActions/CreateAction.js index 2b780466e8..796b3557d6 100644 --- a/src/plugins/formActions/CreateAction.js +++ b/src/plugins/formActions/CreateAction.js @@ -23,7 +23,7 @@ import PropertiesAction from './PropertiesAction'; import CreateWizard from './CreateWizard'; -import uuid from 'uuid'; +import { v4 as uuid } from 'uuid'; export default class CreateAction extends PropertiesAction { constructor(openmct, type, parentDomainObject) { diff --git a/src/plugins/formActions/CreateActionSpec.js b/src/plugins/formActions/CreateActionSpec.js new file mode 100644 index 0000000000..2071da4710 --- /dev/null +++ b/src/plugins/formActions/CreateActionSpec.js @@ -0,0 +1,128 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ +import CreateAction from './CreateAction'; + +import { + createOpenMct, + resetApplicationState +} from 'utils/testing'; + +import { debounce } from 'lodash'; + +let parentObject; +let parentObjectPath; +let unObserve; + +describe("The create action plugin", () => { + let openmct; + + const TYPES = [ + 'clock', + 'conditionWidget', + 'conditionWidget', + 'example.imagery', + 'example.state-generator', + 'flexible-layout', + 'folder', + 'generator', + 'hyperlink', + 'LadTable', + 'LadTableSet', + 'layout', + 'mmgis', + 'notebook', + 'plan', + 'table', + 'tabs', + 'telemetry-mean', + 'telemetry.plot.bar-graph', + 'telemetry.plot.overlay', + 'telemetry.plot.stacked', + 'time-strip', + 'timer', + 'webpage' + ]; + + beforeEach((done) => { + openmct = createOpenMct(); + + openmct.on('start', done); + openmct.startHeadless(); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + describe('creates new objects for a', () => { + beforeEach(() => { + parentObject = { + name: 'mock folder', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + composition: [] + }; + parentObjectPath = [parentObject]; + + spyOn(openmct.objects, 'save'); + openmct.objects.save.and.callThrough(); + spyOn(openmct.forms, 'showForm'); + openmct.forms.showForm.and.callFake(formStructure => { + return Promise.resolve({ + name: 'test', + notes: 'test notes', + location: parentObjectPath + }); + }); + }); + + afterEach(() => { + parentObject = null; + unObserve(); + }); + + TYPES.forEach(type => { + it(`type ${type}`, (done) => { + function callback(newObject) { + const composition = newObject.composition; + + openmct.objects.get(composition[0]) + .then(object => { + expect(object.type).toEqual(type); + expect(object.location).toEqual(openmct.objects.makeKeyString(parentObject.identifier)); + + done(); + }); + } + + const deBouncedCallback = debounce(callback, 300); + unObserve = openmct.objects.observe(parentObject, '*', deBouncedCallback); + + const createAction = new CreateAction(openmct, type, parentObject); + createAction.invoke(); + }); + }); + }); +}); diff --git a/src/plugins/formActions/EditPropertiesAction.js b/src/plugins/formActions/EditPropertiesAction.js index 4937b2ab3d..65ceaaadd1 100644 --- a/src/plugins/formActions/EditPropertiesAction.js +++ b/src/plugins/formActions/EditPropertiesAction.js @@ -45,7 +45,7 @@ export default class EditPropertiesAction extends PropertiesAction { } invoke(objectPath) { - this._showEditForm(objectPath); + return this._showEditForm(objectPath); } /** @@ -76,6 +76,13 @@ export default class EditPropertiesAction extends PropertiesAction { } } + /** + * @private + */ + _onCancel() { + //noop + } + /** * @private */ @@ -86,7 +93,8 @@ export default class EditPropertiesAction extends PropertiesAction { const formStructure = createWizard.getFormStructure(false); formStructure.title = 'Edit ' + this.domainObject.name; - this.openmct.forms.showForm(formStructure) - .then(this._onSave.bind(this)); + return this.openmct.forms.showForm(formStructure) + .then(this._onSave.bind(this)) + .catch(this._onCancel.bind(this)); } } diff --git a/src/plugins/formActions/pluginSpec.js b/src/plugins/formActions/pluginSpec.js new file mode 100644 index 0000000000..232ff0d303 --- /dev/null +++ b/src/plugins/formActions/pluginSpec.js @@ -0,0 +1,229 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ +import { + createMouseEvent, + createOpenMct, + resetApplicationState +} from 'utils/testing'; + +import { debounce } from 'lodash'; + +describe('EditPropertiesAction plugin', () => { + let editPropertiesAction; + let openmct; + let element; + + beforeEach((done) => { + element = document.createElement('div'); + element.style.display = 'block'; + element.style.width = '1920px'; + element.style.height = '1080px'; + + openmct = createOpenMct(); + openmct.on('start', done); + openmct.startHeadless(element); + + editPropertiesAction = openmct.actions.getAction('properties'); + }); + + afterEach(() => { + editPropertiesAction = null; + + return resetApplicationState(openmct); + }); + + it('editPropertiesAction exists', () => { + expect(editPropertiesAction.key).toEqual('properties'); + }); + + it('edit properties action applies to only persistable objects', () => { + spyOn(openmct.objects, 'isPersistable').and.returnValue(true); + + const domainObject = { + name: 'mock folder', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + composition: [] + }; + const isApplicableTo = editPropertiesAction.appliesTo([domainObject]); + expect(isApplicableTo).toBe(true); + }); + + it('edit properties action does not apply to non persistable objects', () => { + spyOn(openmct.objects, 'isPersistable').and.returnValue(false); + + const domainObject = { + name: 'mock folder', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + composition: [] + }; + const isApplicableTo = editPropertiesAction.appliesTo([domainObject]); + expect(isApplicableTo).toBe(false); + }); + + it('edit properties action when invoked shows form', (done) => { + const domainObject = { + name: 'mock folder', + notes: 'mock notes', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + modified: 1643065068597, + persisted: 1643065068600, + composition: [] + }; + + const deBouncedFormChange = debounce(handleFormPropertyChange, 500); + openmct.forms.on('onFormPropertyChange', deBouncedFormChange); + + function handleFormPropertyChange(data) { + const form = document.querySelector('.js-form'); + const title = form.querySelector('input'); + expect(title.value).toEqual(domainObject.name); + + const notes = form.querySelector('textArea'); + expect(notes.value).toEqual(domainObject.notes); + + const buttons = form.querySelectorAll('button'); + expect(buttons[0].textContent.trim()).toEqual('OK'); + expect(buttons[1].textContent.trim()).toEqual('Cancel'); + + const clickEvent = createMouseEvent('click'); + buttons[1].dispatchEvent(clickEvent); + + openmct.forms.off('onFormPropertyChange', deBouncedFormChange); + } + + editPropertiesAction.invoke([domainObject]) + .then(() => { + done(); + }) + .catch(() => { + done(); + }); + }); + + it('edit properties action saves changes', (done) => { + const oldName = 'mock folder'; + const newName = 'renamed mock folder'; + const domainObject = { + name: oldName, + notes: 'mock notes', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + modified: 1643065068597, + persisted: 1643065068600, + composition: [] + }; + let unObserve; + + function callback(newObject) { + expect(newObject.name).not.toEqual(oldName); + expect(newObject.name).toEqual(newName); + + unObserve(); + done(); + } + + const deBouncedCallback = debounce(callback, 300); + unObserve = openmct.objects.observe(domainObject, '*', deBouncedCallback); + + let changed = false; + const deBouncedFormChange = debounce(handleFormPropertyChange, 500); + openmct.forms.on('onFormPropertyChange', deBouncedFormChange); + + function handleFormPropertyChange(data) { + const form = document.querySelector('.js-form'); + const title = form.querySelector('input'); + const notes = form.querySelector('textArea'); + + const buttons = form.querySelectorAll('button'); + expect(buttons[0].textContent.trim()).toEqual('OK'); + expect(buttons[1].textContent.trim()).toEqual('Cancel'); + + if (!changed) { + expect(title.value).toEqual(domainObject.name); + expect(notes.value).toEqual(domainObject.notes); + + // change input field value and dispatch event for it + title.focus(); + title.value = newName; + title.dispatchEvent(new Event('input')); + title.blur(); + + changed = true; + } else { + // click ok to save form changes + const clickEvent = createMouseEvent('click'); + buttons[0].dispatchEvent(clickEvent); + + openmct.forms.off('onFormPropertyChange', deBouncedFormChange); + } + } + + editPropertiesAction.invoke([domainObject]); + }); + + it('edit properties action discards changes', (done) => { + const name = 'mock folder'; + const domainObject = { + name, + notes: 'mock notes', + type: 'folder', + identifier: { + key: 'mock-folder', + namespace: '' + }, + modified: 1643065068597, + persisted: 1643065068600, + composition: [] + }; + + editPropertiesAction.invoke([domainObject]) + .then(() => { + expect(domainObject.name).toEqual(name); + done(); + }) + .catch(() => { + expect(domainObject.name).toEqual(name); + + done(); + }); + + const form = document.querySelector('.js-form'); + const buttons = form.querySelectorAll('button'); + const clickEvent = createMouseEvent('click'); + buttons[1].dispatchEvent(clickEvent); + }); +}); diff --git a/src/plugins/gauge/GaugePlugin.js b/src/plugins/gauge/GaugePlugin.js index c9db912df1..441e53cd57 100644 --- a/src/plugins/gauge/GaugePlugin.js +++ b/src/plugins/gauge/GaugePlugin.js @@ -49,6 +49,7 @@ export default function () { gaugeType: GAUGE_TYPES[0][1], isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: true, limitLow: 10, limitHigh: 90, @@ -59,6 +60,23 @@ export default function () { }; }, form: [ + { + name: "Gauge type", + options: GAUGE_TYPES.map(type => { + return { + name: type[0], + value: type[1] + }; + }), + control: "select", + cssClass: "l-input-sm", + key: "gaugeController", + property: [ + "configuration", + "gaugeController", + "gaugeType" + ] + }, { name: "Display current value", control: "toggleSwitch", @@ -70,6 +88,17 @@ export default function () { "isDisplayCurVal" ] }, + { + name: "Display units", + control: "toggleSwitch", + cssClass: "l-input", + key: "isDisplayUnits", + property: [ + "configuration", + "gaugeController", + "isDisplayUnits" + ] + }, { name: "Display range values", control: "toggleSwitch", @@ -92,23 +121,6 @@ export default function () { "precision" ] }, - { - name: "Gauge type", - options: GAUGE_TYPES.map(type => { - return { - name: type[0], - value: type[1] - }; - }), - control: "select", - cssClass: "l-input-sm", - key: "gaugeController", - property: [ - "configuration", - "gaugeController", - "gaugeType" - ] - }, { name: "Value ranges and limits", control: "gauge-controller", diff --git a/src/plugins/gauge/GaugePluginSpec.js b/src/plugins/gauge/GaugePluginSpec.js index 5894498063..601c2bc7ff 100644 --- a/src/plugins/gauge/GaugePluginSpec.js +++ b/src/plugins/gauge/GaugePluginSpec.js @@ -63,30 +63,30 @@ describe('Gauge plugin', () => { }); it('Plugin installed by default', () => { - const gaugueType = openmct.types.get('gauge'); + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType).not.toBeNull(); - expect(gaugueType.definition.name).toEqual('Gauge'); + expect(GaugeType).not.toBeNull(); + expect(GaugeType.definition.name).toEqual('Gauge'); }); - it('Gaugue plugin is creatable', () => { - const gaugueType = openmct.types.get('gauge'); + it('Gauge plugin is creatable', () => { + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType.definition.creatable).toBeTrue(); + expect(GaugeType.definition.creatable).toBeTrue(); }); - it('Gaugue plugin is creatable', () => { - const gaugueType = openmct.types.get('gauge'); + it('Gauge plugin is creatable', () => { + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType.definition.creatable).toBeTrue(); + expect(GaugeType.definition.creatable).toBeTrue(); }); - it('Gaugue form controller', () => { + it('Gauge form controller', () => { const gaugeController = openmct.forms.getFormControl('gauge-controller'); expect(gaugeController).toBeDefined(); }); - describe('Gaugue with Filled Dial', () => { + describe('Gauge with Filled Dial', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -105,6 +105,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-filled', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -222,7 +223,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Needle Dial', () => { + describe('Gauge with Needle Dial', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -240,6 +241,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-needle', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -357,7 +359,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Vertical Meter', () => { + describe('Gauge with Vertical Meter', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -375,6 +377,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -469,7 +472,7 @@ describe('Gauge plugin', () => { it('renders major elements', () => { const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range'); - const valueElement = gaugeHolder.querySelector('.js-meter-current-value'); + const valueElement = gaugeHolder.querySelector('.js-gauge-current-value'); const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); @@ -482,7 +485,7 @@ describe('Gauge plugin', () => { it('renders correct current value', (done) => { function WatchUpdateValue() { - const textElement = gaugeHolder.querySelector('.js-meter-current-value'); + const textElement = gaugeHolder.querySelector('.js-gauge-current-value'); expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision)); done(); } @@ -492,7 +495,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Vertical Meter Inverted', () => { + describe('Gauge with Vertical Meter Inverted', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -506,6 +509,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -566,7 +570,7 @@ describe('Gauge plugin', () => { it('renders major elements', () => { const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range'); - const valueElement = gaugeHolder.querySelector('.js-meter-current-value'); + const valueElement = gaugeHolder.querySelector('.js-gauge-current-value'); const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); @@ -574,7 +578,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Horizontal Meter', () => { + describe('Gauge with Horizontal Meter', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -588,6 +592,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -656,7 +661,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Filled Dial with Use Telemetry Limits', () => { + describe('Gauge with Filled Dial with Use Telemetry Limits', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -673,6 +678,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-filled', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: true, limitLow: 10, limitHigh: 90, diff --git a/src/plugins/gauge/components/Gauge.vue b/src/plugins/gauge/components/Gauge.vue index 91036ce965..2d250158eb 100644 --- a/src/plugins/gauge/components/Gauge.vue +++ b/src/plugins/gauge/components/Gauge.vue @@ -23,179 +23,218 @@
@@ -209,20 +248,33 @@
{{ rangeLow }}
+
+