diff --git a/e2e/tests/plugins/gauge.e2eSpec.js b/e2e/tests/plugins/gauge.e2eSpec.js
new file mode 100644
index 0000000000..3f53ec40e4
--- /dev/null
+++ b/e2e/tests/plugins/gauge.e2eSpec.js
@@ -0,0 +1,31 @@
+// 1. create an gauge plugin
+// 2. create with custom settings
+// 3. verify required fields are required
+// 4. should not be able to create gaugue inside a gauge.
+// 5. should not be able to move/duplicate gaugue inside a gauge.
+// 6. delete gauge
+// 7. snapshot gauge
+// 8. snapshot gauge inside Notebook entry
+// 9. gauge inside Notebook entry
+// 10. can drop inside other objects:
+// can drop to display layout : yes
+// can drop to Flexible layout : yes
+// can drop to Folder : yes
+// can drop to Bar graph: No
+// can drop to clock: No
+// . can drop to condition set: No
+// . can drop to condition widegt: No
+// // .............all other objects
+// . can drop to webpage: No
+// 11. drop telemetry inside a gauge and reflect on data in both fixed and realtime
+// 12. can have only one telemetry per gague. (show confirmation dialog if want to replace)
+// 12. all form props (except hidefromInspector) shows inside inspector
+// 13. should be able to export and import
+// 14. able to search in tree
+// 15. supports fix and realtime mode
+// 17. refresh after creating
+// 18. open in new tab
+// 19. two separate gauges inside display layout should not show same numbers unless have same telemetry (should show respctive telemetry data).
+// 20. inside display layout compare gague values vs its telemetry value (should be same in fixed time and should tick with same rate/value in realtime)
+// 21. same for two diff gauges- > inside display layout compare gague values vs its telemetry value (should be same in fixed time and should tick with same rate/value in realtime)
+// 22. export jpeg/png of plugin
diff --git a/index.html b/index.html
index 105b2d49e0..5aae47c16f 100644
--- a/index.html
+++ b/index.html
@@ -77,7 +77,7 @@
openmct.install(openmct.plugins.LocalStorage());
-
+
openmct.install(openmct.plugins.example.Generator());
openmct.install(openmct.plugins.example.EventGeneratorPlugin());
openmct.install(openmct.plugins.example.ExampleImagery());
diff --git a/src/MCT.js b/src/MCT.js
index e83cf89fb0..19700d691f 100644
--- a/src/MCT.js
+++ b/src/MCT.js
@@ -242,6 +242,7 @@ define([
// Plugins that are installed by default
+ this.install(this.plugins.Gauge());
this.install(this.plugins.Plot());
this.install(this.plugins.Chart());
this.install(this.plugins.TelemetryTable.default());
diff --git a/src/api/forms/FormController.js b/src/api/forms/FormController.js
index 95a5b4a28f..68e195f4eb 100644
--- a/src/api/forms/FormController.js
+++ b/src/api/forms/FormController.js
@@ -1,5 +1,6 @@
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
+import CheckBoxField from './components/controls/CheckBoxField.vue';
import Datetime from './components/controls/Datetime.vue';
import FileInput from './components/controls/FileInput.vue';
import Locator from './components/controls/Locator.vue';
@@ -7,11 +8,13 @@ import NumberField from './components/controls/NumberField.vue';
import SelectField from './components/controls/SelectField.vue';
import TextAreaField from './components/controls/TextAreaField.vue';
import TextField from './components/controls/TextField.vue';
+import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
import Vue from 'vue';
export const DEFAULT_CONTROLS_MAP = {
'autocomplete': AutoCompleteField,
+ 'checkbox': CheckBoxField,
'composite': ClockDisplayFormatField,
'datetime': Datetime,
'file-input': FileInput,
@@ -19,7 +22,8 @@ export const DEFAULT_CONTROLS_MAP = {
'numberfield': NumberField,
'select': SelectField,
'textarea': TextAreaField,
- 'textfield': TextField
+ 'textfield': TextField,
+ 'toggleSwitch': ToggleSwitchField
};
export default class FormControl {
@@ -94,4 +98,3 @@ export default class FormControl {
};
}
}
-
diff --git a/src/api/forms/components/FormRow.vue b/src/api/forms/components/FormRow.vue
index 800ca7c589..c6e3aba454 100644
--- a/src/api/forms/components/FormRow.vue
+++ b/src/api/forms/components/FormRow.vue
@@ -79,10 +79,12 @@ export default {
rowClass() {
let cssClass = this.cssClass;
- if (this.row.required) {
- cssClass = `${cssClass} req`;
+ if (!this.row.required) {
+ return;
}
+ cssClass = `${cssClass} req`;
+
if (this.visited && this.valid !== undefined) {
if (this.valid === true) {
cssClass = `${cssClass} valid`;
diff --git a/src/api/forms/components/controls/CheckBoxField.vue b/src/api/forms/components/controls/CheckBoxField.vue
new file mode 100644
index 0000000000..bfff992bd6
--- /dev/null
+++ b/src/api/forms/components/controls/CheckBoxField.vue
@@ -0,0 +1,55 @@
+/*****************************************************************************
+* 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/api/forms/components/controls/NumberField.vue b/src/api/forms/components/controls/NumberField.vue
index e552b492c9..d4edd2b1b3 100644
--- a/src/api/forms/components/controls/NumberField.vue
+++ b/src/api/forms/components/controls/NumberField.vue
@@ -58,7 +58,6 @@ export default {
},
methods: {
updateText() {
- console.log('updateText', this.field);
const data = {
model: this.model,
value: this.field
diff --git a/src/api/forms/components/controls/ToggleSwitchField.vue b/src/api/forms/components/controls/ToggleSwitchField.vue
new file mode 100644
index 0000000000..0c3a3e1186
--- /dev/null
+++ b/src/api/forms/components/controls/ToggleSwitchField.vue
@@ -0,0 +1,62 @@
+/*****************************************************************************
+* 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/api/forms/toggle-check-box-mixin.js b/src/api/forms/toggle-check-box-mixin.js
new file mode 100644
index 0000000000..f15c344039
--- /dev/null
+++ b/src/api/forms/toggle-check-box-mixin.js
@@ -0,0 +1,19 @@
+export default {
+ data() {
+ return {
+ isChecked: false
+ };
+ },
+ methods: {
+ toggleCheckBox(event) {
+ this.isChecked = !this.isChecked;
+
+ const data = {
+ model: this.model,
+ value: this.isChecked
+ };
+
+ this.$emit('onChange', data);
+ }
+ }
+};
diff --git a/src/plugins/formActions/CreateWizard.js b/src/plugins/formActions/CreateWizard.js
index 8921a100ba..d17bdeb6bd 100644
--- a/src/plugins/formActions/CreateWizard.js
+++ b/src/plugins/formActions/CreateWizard.js
@@ -90,6 +90,9 @@ export default class CreateWizard {
rows: this.properties.map(property => {
const row = JSON.parse(JSON.stringify(property));
row.value = this.getValue(row);
+ if (property.validate) {
+ row.validate = property.validate;
+ }
return row;
}).filter(row => row && row.control)
diff --git a/src/plugins/gauge/GaugePlugin.js b/src/plugins/gauge/GaugePlugin.js
new file mode 100644
index 0000000000..c9db912df1
--- /dev/null
+++ b/src/plugins/gauge/GaugePlugin.js
@@ -0,0 +1,199 @@
+/*****************************************************************************
+ * 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 GaugeViewProvider from './GaugeViewProvider';
+import GaugeFormController from './components/GaugeFormController.vue';
+import Vue from 'vue';
+
+export const GAUGE_TYPES = [
+ ['Filled Dial', 'dial-filled'],
+ ['Needle Dial', 'dial-needle'],
+ ['Vertical Meter', 'meter-vertical'],
+ ['Vertical Meter Inverted', 'meter-vertical-inverted'],
+ ['Horizontal Meter', 'meter-horizontal']
+];
+
+export default function () {
+ return function install(openmct) {
+ openmct.objectViews.addProvider(new GaugeViewProvider(openmct));
+
+ openmct.forms.addNewFormControl('gauge-controller', getGaugeFormController(openmct));
+ openmct.types.addType('gauge', {
+ name: "Gauge",
+ creatable: true,
+ description: "Graphically visualize a telemetry element's current value between a minimum and maximum.",
+ cssClass: 'icon-gauge',
+ initialize(domainObject) {
+ domainObject.composition = [];
+ domainObject.configuration = {
+ gaugeController: {
+ gaugeType: GAUGE_TYPES[0][1],
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: true,
+ limitLow: 10,
+ limitHigh: 90,
+ max: 100,
+ min: 0,
+ precision: 2
+ }
+ };
+ },
+ form: [
+ {
+ name: "Display current value",
+ control: "toggleSwitch",
+ cssClass: "l-input",
+ key: "isDisplayCurVal",
+ property: [
+ "configuration",
+ "gaugeController",
+ "isDisplayCurVal"
+ ]
+ },
+ {
+ name: "Display range values",
+ control: "toggleSwitch",
+ cssClass: "l-input",
+ key: "isDisplayMinMax",
+ property: [
+ "configuration",
+ "gaugeController",
+ "isDisplayMinMax"
+ ]
+ },
+ {
+ name: "Float precision",
+ control: "numberfield",
+ cssClass: "l-input-sm",
+ key: "precision",
+ property: [
+ "configuration",
+ "gaugeController",
+ "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",
+ cssClass: "l-input",
+ key: "gaugeController",
+ required: false,
+ hideFromInspector: true,
+ property: [
+ "configuration",
+ "gaugeController"
+ ],
+ validate: ({ value }, callback) => {
+ if (value.isUseTelemetryLimits) {
+ return true;
+ }
+
+ const { min, max, limitLow, limitHigh } = value;
+ const valid = {
+ min: true,
+ max: true,
+ limitLow: true,
+ limitHigh: true
+ };
+
+ if (min === '') {
+ valid.min = false;
+ }
+
+ if (max === '') {
+ valid.max = false;
+ }
+
+ if (max < min) {
+ valid.min = false;
+ valid.max = false;
+ }
+
+ if (limitLow !== '') {
+ valid.limitLow = min <= limitLow && limitLow < max;
+ }
+
+ if (limitHigh !== '') {
+ valid.limitHigh = min < limitHigh && limitHigh <= max;
+ }
+
+ if (valid.limitLow && valid.limitHigh
+ && limitLow !== '' && limitHigh !== ''
+ && limitLow > limitHigh) {
+ valid.limitLow = false;
+ valid.limitHigh = false;
+ }
+
+ if (callback) {
+ callback(valid);
+ }
+
+ return valid.min && valid.max && valid.limitLow && valid.limitHigh;
+ }
+ }
+ ]
+ });
+ };
+
+ function getGaugeFormController(openmct) {
+ return {
+ show(element, model, onChange) {
+ const rowComponent = new Vue({
+ el: element,
+ components: {
+ GaugeFormController
+ },
+ provide: {
+ openmct
+ },
+ data() {
+ return {
+ model,
+ onChange
+ };
+ },
+ template: ``
+ });
+
+ return rowComponent;
+ }
+ };
+ }
+}
diff --git a/src/plugins/gauge/GaugePluginSpec.js b/src/plugins/gauge/GaugePluginSpec.js
new file mode 100644
index 0000000000..982cb58c49
--- /dev/null
+++ b/src/plugins/gauge/GaugePluginSpec.js
@@ -0,0 +1,805 @@
+/*****************************************************************************
+ * 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 { debounce } from 'lodash';
+
+import Vue from 'vue';
+
+let gaugeDomainObject = {
+ identifier: {
+ key: 'gauge',
+ namespace: 'test-namespace'
+ },
+ type: 'gauge',
+ composition: []
+};
+
+describe('Gauge plugin', () => {
+ let openmct;
+ let child;
+ let gaugeHolder;
+
+ beforeEach((done) => {
+ gaugeHolder = document.createElement('div');
+ gaugeHolder.style.display = 'block';
+ gaugeHolder.style.width = '1920px';
+ gaugeHolder.style.height = '1080px';
+
+ child = document.createElement('div');
+ gaugeHolder.appendChild(child);
+
+ openmct = createOpenMct();
+ openmct.on('start', done);
+
+ openmct.startHeadless();
+ });
+
+ afterEach(() => {
+ return resetApplicationState(openmct);
+ });
+
+ it('Plugin installed by default', () => {
+ const gaugueType = openmct.types.get('gauge');
+
+ expect(gaugueType).not.toBeNull();
+ expect(gaugueType.definition.name).toEqual('Gauge');
+ });
+
+ it('Gaugue plugin is creatable', () => {
+ const gaugueType = openmct.types.get('gauge');
+
+ expect(gaugueType.definition.creatable).toBeTrue();
+ });
+
+ it('Gaugue plugin is creatable', () => {
+ const gaugueType = openmct.types.get('gauge');
+
+ expect(gaugueType.definition.creatable).toBeTrue();
+ });
+
+ it('Gaugue form controller', () => {
+ const gaugeController = openmct.forms.getFormControl('gauge-controller');
+ expect(gaugeController).toBeDefined();
+ });
+
+ describe('Gaugue with Filled Dial', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+ let randomValue;
+
+ const minValue = -1;
+ const maxValue = 1;
+
+ beforeEach(() => {
+ randomValue = Math.random();
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'dial-filled',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: false,
+ limitLow: -0.9,
+ limitHigh: 0.9,
+ max: maxValue,
+ min: minValue,
+ precision: 2
+ }
+ },
+ composition: [
+ {
+ namespace: 'test-namespace',
+ key: 'test-object'
+ }
+ ],
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
+ valuesForHints: () => {
+ return [
+ {
+ source: 'sin'
+ }
+ ];
+ },
+ value: () => 1
+ });
+ spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
+ parse: () => {
+ return 2000;
+ }
+ });
+ spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
+ sin: {
+ format: (datum) => {
+ return randomValue;
+ }
+ }
+ });
+ spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
+ spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
+ spyOn(openmct.time, 'bounds').and.returnValue({
+ start: 1000,
+ end: 5000
+ });
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
+ const dialElement = gaugeHolder.querySelector('.c-dial');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+
+ it('renders correct min max values', () => {
+ expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
+ });
+
+ it('renders correct current value', (done) => {
+ function WatchUpdateValue() {
+ const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
+ expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
+ done();
+ }
+
+ const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
+ Vue.nextTick(debouncedWatchUpdate);
+ });
+ });
+
+ describe('Gaugue with Needle Dial', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+ let randomValue;
+
+ const minValue = -1;
+ const maxValue = 1;
+ beforeEach(() => {
+ randomValue = Math.random();
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'dial-needle',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: false,
+ limitLow: -0.9,
+ limitHigh: 0.9,
+ max: maxValue,
+ min: minValue,
+ precision: 2
+ }
+ },
+ composition: [
+ {
+ namespace: 'test-namespace',
+ key: 'test-object'
+ }
+ ],
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
+ valuesForHints: () => {
+ return [
+ {
+ source: 'sin'
+ }
+ ];
+ },
+ value: () => 1
+ });
+ spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
+ parse: () => {
+ return 2000;
+ }
+ });
+ spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
+ sin: {
+ format: (datum) => {
+ return randomValue;
+ }
+ }
+ });
+ spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
+ spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
+ spyOn(openmct.time, 'bounds').and.returnValue({
+ start: 1000,
+ end: 5000
+ });
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
+ const dialElement = gaugeHolder.querySelector('.c-dial');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+
+ it('renders correct min max values', () => {
+ expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`);
+ });
+
+ it('renders correct current value', (done) => {
+ function WatchUpdateValue() {
+ const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
+ expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
+ done();
+ }
+
+ const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
+ Vue.nextTick(debouncedWatchUpdate);
+ });
+ });
+
+ describe('Gaugue with Vertical Meter', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+ let randomValue;
+
+ const minValue = -1;
+ const maxValue = 1;
+ beforeEach(() => {
+ randomValue = Math.random();
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'meter-vertical',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: false,
+ limitLow: -0.9,
+ limitHigh: 0.9,
+ max: maxValue,
+ min: minValue,
+ precision: 2
+ }
+ },
+ composition: [
+ {
+ namespace: 'test-namespace',
+ key: 'test-object'
+ }
+ ],
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
+ valuesForHints: () => {
+ return [
+ {
+ source: 'sin'
+ }
+ ];
+ },
+ value: () => 1
+ });
+ spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
+ parse: () => {
+ return 2000;
+ }
+ });
+ spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
+ sin: {
+ format: (datum) => {
+ return randomValue;
+ }
+ }
+ });
+ spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
+ spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
+ spyOn(openmct.time, 'bounds').and.returnValue({
+ start: 1000,
+ end: 5000
+ });
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-meter');
+ const dialElement = gaugeHolder.querySelector('.c-meter__bg');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+
+ it('renders correct min max values', () => {
+ expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${maxValue} ${minValue}`);
+ });
+
+ it('renders correct current value', (done) => {
+ function WatchUpdateValue() {
+ const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
+ expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
+ done();
+ }
+
+ const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
+ Vue.nextTick(debouncedWatchUpdate);
+ });
+ });
+
+ describe('Gaugue with Vertical Meter Inverted', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+
+ beforeEach(() => {
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'meter-vertical',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: false,
+ limitLow: -0.9,
+ limitHigh: 0.9,
+ max: 1,
+ min: -1,
+ precision: 2
+ }
+ },
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-meter');
+ const dialElement = gaugeHolder.querySelector('.c-meter__bg');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+ });
+
+ describe('Gaugue with Horizontal Meter', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+
+ beforeEach(() => {
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'meter-vertical',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: false,
+ limitLow: -0.9,
+ limitHigh: 0.9,
+ max: 1,
+ min: -1,
+ precision: 2
+ }
+ },
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-meter');
+ const dialElement = gaugeHolder.querySelector('.c-meter__bg');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+ });
+
+ describe('Gaugue with Filled Dial with Use Telemetry Limits', () => {
+ let gaugeViewProvider;
+ let gaugeView;
+ let gaugeViewObject;
+ let mutablegaugeObject;
+ let randomValue;
+
+ beforeEach(() => {
+ randomValue = Math.random();
+
+ gaugeViewObject = {
+ ...gaugeDomainObject,
+ configuration: {
+ gaugeController: {
+ gaugeType: 'dial-filled',
+ isDisplayMinMax: true,
+ isDisplayCurVal: true,
+ isUseTelemetryLimits: true,
+ limitLow: 10,
+ limitHigh: 90,
+ max: 100,
+ min: 0,
+ precision: 2
+ }
+ },
+ composition: [
+ {
+ namespace: 'test-namespace',
+ key: 'test-object'
+ }
+ ],
+ id: 'test-object',
+ name: 'gauge'
+ };
+
+ const testObjectProvider = jasmine.createSpyObj('testObjectProvider', [
+ 'get',
+ 'create',
+ 'update',
+ 'observe'
+ ]);
+
+ openmct.editor = {};
+ openmct.editor.isEditing = () => false;
+
+ const applicableViews = openmct.objectViews.get(gaugeViewObject, [gaugeViewObject]);
+ gaugeViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'gauge');
+
+ testObjectProvider.get.and.returnValue(Promise.resolve(gaugeViewObject));
+ testObjectProvider.create.and.returnValue(Promise.resolve(gaugeViewObject));
+ openmct.objects.addProvider('test-namespace', testObjectProvider);
+ testObjectProvider.observe.and.returnValue(() => {});
+ testObjectProvider.create.and.returnValue(Promise.resolve(true));
+ testObjectProvider.update.and.returnValue(Promise.resolve(true));
+
+ spyOn(openmct.telemetry, 'getMetadata').and.returnValue({
+ valuesForHints: () => {
+ return [
+ {
+ source: 'sin'
+ }
+ ];
+ },
+ value: () => 1
+ });
+ spyOn(openmct.telemetry, 'getValueFormatter').and.returnValue({
+ parse: () => {
+ return 2000;
+ }
+ });
+ spyOn(openmct.telemetry, 'getFormatMap').and.returnValue({
+ sin: {
+ format: (datum) => {
+ return randomValue;
+ }
+ }
+ });
+ spyOn(openmct.telemetry, 'getLimits').and.returnValue(
+ {
+ limits: () => Promise.resolve({
+ CRITICAL: {
+ high: 0.99,
+ low: -0.99
+ }
+ })
+ }
+ );
+ spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
+ spyOn(openmct.time, 'bounds').and.returnValue({
+ start: 1000,
+ end: 5000
+ });
+
+ return openmct.objects.getMutable(gaugeViewObject.identifier).then((mutableObject) => {
+ mutablegaugeObject = mutableObject;
+ gaugeView = gaugeViewProvider.view(mutablegaugeObject);
+ gaugeView.show(child);
+
+ return Vue.nextTick();
+ });
+ });
+
+ afterEach(() => {
+ gaugeView.destroy();
+
+ return resetApplicationState(openmct);
+ });
+
+ it('provides gauge view', () => {
+ expect(gaugeViewProvider).toBeDefined();
+ });
+
+ it('renders gauge element', () => {
+ const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge');
+ expect(gaugeElement.length).toBe(1);
+ });
+
+ it('renders major elements', () => {
+ const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper');
+ const rangeElement = gaugeHolder.querySelector('.c-gauge__range');
+ const curveElement = gaugeHolder.querySelector('.c-gauge__curval');
+ const dialElement = gaugeHolder.querySelector('.c-dial');
+
+ const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement);
+
+ expect(hasMajorElements).toBe(true);
+ });
+
+ it('renders correct min max values', () => {
+ expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`);
+ });
+
+ it('renders correct current value', (done) => {
+ function WatchUpdateValue() {
+ const textElement = gaugeHolder.querySelector('.c-gauge__curval-text');
+ expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision));
+ done();
+ }
+
+ const debouncedWatchUpdate = debounce(WatchUpdateValue, 200);
+ Vue.nextTick(debouncedWatchUpdate);
+ });
+ });
+});
diff --git a/src/plugins/gauge/GaugeViewProvider.js b/src/plugins/gauge/GaugeViewProvider.js
new file mode 100644
index 0000000000..b70acd2ae7
--- /dev/null
+++ b/src/plugins/gauge/GaugeViewProvider.js
@@ -0,0 +1,65 @@
+/*****************************************************************************
+ * 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 GaugeComponent from './components/Gauge.vue';
+import Vue from 'vue';
+
+export default function GaugeViewProvider(openmct) {
+ return {
+ key: 'gauge',
+ name: 'Gauge',
+ cssClass: 'icon-gauge',
+ canView: function (domainObject) {
+ return domainObject.type === 'gauge';
+ },
+ canEdit: function (domainObject) {
+ return false;
+ },
+ view: function (domainObject) {
+ let component;
+
+ return {
+ show: function (element) {
+ component = new Vue({
+ el: element,
+ components: {
+ GaugeComponent
+ },
+ provide: {
+ openmct,
+ domainObject,
+ composition: openmct.composition.get(domainObject)
+ },
+ template: ''
+ });
+ },
+ destroy: function (element) {
+ component.$destroy();
+ component = undefined;
+ }
+ };
+ },
+ priority: function () {
+ return 1;
+ }
+ };
+}
diff --git a/src/plugins/gauge/components/Gauge.vue b/src/plugins/gauge/components/Gauge.vue
new file mode 100644
index 0000000000..8c6d335156
--- /dev/null
+++ b/src/plugins/gauge/components/Gauge.vue
@@ -0,0 +1,467 @@
+/*****************************************************************************
+* 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.
+*****************************************************************************/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ rangeHigh }}
+
{{ rangeLow }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plugins/gauge/components/GaugeFormController.vue b/src/plugins/gauge/components/GaugeFormController.vue
new file mode 100644
index 0000000000..949c21018c
--- /dev/null
+++ b/src/plugins/gauge/components/GaugeFormController.vue
@@ -0,0 +1,170 @@
+/*****************************************************************************
+* 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.
+*****************************************************************************/
+
+
+
+
+
+
+