Compare commits
	
		
			104 Commits
		
	
	
		
			test-heuri
			...
			joel/plot-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a1d1301542 | ||
|   | aef8589872 | ||
|   | 38f3e60884 | ||
|   | 331c086a60 | ||
|   | 0890ebfd0f | ||
|   | 1547ea5cf7 | ||
|   | b1551478e4 | ||
|   | 213c469758 | ||
|   | 03ac43d306 | ||
|   | f3de6f2548 | ||
|   | 62dfb7d8f9 | ||
|   | 0a33cbeea2 | ||
|   | 4908d5afb0 | ||
|   | aa4bf5eaf6 | ||
|   | 4e4031e700 | ||
|   | 46a20bb76d | ||
|   | 1d482f318e | ||
|   | 6826b579a6 | ||
|   | 3d4c721232 | ||
|   | 190d34c939 | ||
|   | 47bc0e9793 | ||
|   | 35115aaa50 | ||
|   | b252051c83 | ||
|   | 4ca1181eb6 | ||
|   | f973f42729 | ||
|   | 4788561631 | ||
|   | e8699d54d5 | ||
|   | 14bc49451b | ||
|   | dd2e8c0460 | ||
|   | 031753a9a8 | ||
|   | 8edae5f02c | ||
|   | 7ed3de01b9 | ||
|   | 4a1931f594 | ||
|   | f174dcc2f6 | ||
|   | 0c741c67f8 | ||
|   | 85da5e43b3 | ||
|   | d26e45f636 | ||
|   | 0bd6814097 | ||
|   | 4e7410b4bf | ||
|   | 436550adba | ||
|   | f6b1be0486 | ||
|   | eca12201c7 | ||
|   | 68ff69664b | ||
|   | 51fbb9cee6 | ||
|   | 437156e04c | ||
|   | d244ee622a | ||
|   | 043d6aa9c3 | ||
|   | ecfab8f7f3 | ||
|   | 05c38c37aa | ||
|   | ce78925119 | ||
|   | b30018c315 | ||
|   | 26aca0f433 | ||
|   | eb5eb7d540 | ||
|   | 609a47c62c | ||
|   | 41259bbd40 | ||
|   | 580640ff47 | ||
|   | 688a03c8ac | ||
|   | 4f12f41685 | ||
|   | a4aec5d492 | ||
|   | 00b3f3ac0b | ||
|   | 189882afc8 | ||
|   | c185f77a15 | ||
|   | 2b3541a323 | ||
|   | 7c9a140481 | ||
|   | 5e9b313ee9 | ||
|   | c64db6c07d | ||
|   | 9e2751acf7 | ||
|   | 51fb72dc04 | ||
|   | d51052ab46 | ||
|   | 0dff431f4a | ||
|   | f62010fb99 | ||
|   | 61d238a097 | ||
|   | ba4ef43673 | ||
|   | f6c16b7483 | ||
|   | 58e63e649f | ||
|   | 7a04aea90e | ||
|   | 0bb475327c | ||
|   | c474b998f0 | ||
|   | f9deb80350 | ||
|   | a02d421093 | ||
|   | 9026099fd2 | ||
|   | 295ccea195 | ||
|   | 021d730814 | ||
|   | ae62b15abf | ||
|   | ba41c1a30e | ||
|   | b9a85d9c4d | ||
|   | 80eab8bad1 | ||
|   | b2d8d640ae | ||
|   | 56e6fa66c2 | ||
|   | 9fa4707c82 | ||
|   | 7e2cfa36de | ||
|   | aaa60a1545 | ||
|   | 717231fed2 | ||
|   | 7fb2bc9729 | ||
|   | addeb635e9 | ||
|   | 608d63a7b0 | ||
|   | 10679e5f4f | ||
|   | 38b8f03b1a | ||
|   | 779a42c28c | ||
|   | 80c2504768 | ||
|   | 80359e3f16 | ||
|   | 66aa4f099f | ||
|   | aa6c6cb88b | ||
|   | 4e5cc840d7 | 
| @@ -2,7 +2,9 @@ | ||||
|   "name": "openmct", | ||||
|   "version": "1.0.0-snapshot", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": {}, | ||||
|   "dependencies": { | ||||
|     "plotly.js-dist": "^1.54.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "angular": "1.7.9", | ||||
|     "angular-route": "1.4.14", | ||||
|   | ||||
| @@ -252,6 +252,7 @@ define([ | ||||
|         // Plugin's that are installed by default | ||||
|  | ||||
|         this.install(this.plugins.Plot()); | ||||
|         this.install(this.plugins.PlotlyPlot()); | ||||
|         this.install(this.plugins.TelemetryTable()); | ||||
|         this.install(PreviewPlugin.default()); | ||||
|         this.install(LegacyIndicatorsPlugin()); | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/plugins/LADTable/LADTableCompositionPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/plugins/LADTable/LADTableCompositionPolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| export default class LADTableCompositionPolicy { | ||||
|  | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|         return this.allow.bind(this); | ||||
|     } | ||||
|  | ||||
|     allow(parent, child) { | ||||
|         if(parent.type === 'LadTable') { | ||||
|             return this.openmct.telemetry.isTelemetryObject(child); | ||||
|         } else if(parent.type === 'LadTableSet') { | ||||
|             return child.type === 'LadTable'; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @@ -19,53 +19,46 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| import LadTableSet from './components/LadTableSet.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| define([ | ||||
|     './components/LadTableSet.vue', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     LadTableSet, | ||||
|     Vue | ||||
| ) { | ||||
|     function LADTableSetViewProvider(openmct) { | ||||
|         return { | ||||
|             key: 'LadTableSet', | ||||
|             name: 'LAD Table Set', | ||||
|             cssClass: 'icon-tabular-lad-set', | ||||
|             canView: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTableSet'; | ||||
|             }, | ||||
|             canEdit: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTableSet'; | ||||
|             }, | ||||
|             view: function (domainObject, objectPath) { | ||||
|                 let component; | ||||
| export default function LADTableSetViewProvider(openmct) { | ||||
|     return { | ||||
|         key: 'LadTableSet', | ||||
|         name: 'LAD Table Set', | ||||
|         cssClass: 'icon-tabular-lad-set', | ||||
|         canView: function (domainObject) { | ||||
|             return domainObject.type === 'LadTableSet'; | ||||
|         }, | ||||
|         canEdit: function (domainObject) { | ||||
|             return domainObject.type === 'LadTableSet'; | ||||
|         }, | ||||
|         view: function (domainObject, objectPath) { | ||||
|             let component; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component =  new Vue({ | ||||
|                             el: element, | ||||
|                             components: { | ||||
|                                 LadTableSet: LadTableSet.default | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             template: '<lad-table-set></lad-table-set>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     return LADTableSetViewProvider; | ||||
| }); | ||||
|             return { | ||||
|                 show: function (element) { | ||||
|                     component =  new Vue({ | ||||
|                         el: element, | ||||
|                         components: { | ||||
|                             LadTableSet: LadTableSet | ||||
|                         }, | ||||
|                         provide: { | ||||
|                             openmct, | ||||
|                             domainObject, | ||||
|                             objectPath | ||||
|                         }, | ||||
|                         template: '<lad-table-set></lad-table-set>' | ||||
|                     }); | ||||
|                 }, | ||||
|                 destroy: function (element) { | ||||
|                     component.$destroy(); | ||||
|                     component = undefined; | ||||
|                 } | ||||
|             }; | ||||
|         }, | ||||
|         priority: function () { | ||||
|             return 1; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -19,53 +19,46 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| import LadTable from './components/LADTable.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| define([ | ||||
|     './components/LADTable.vue', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     LadTableComponent, | ||||
|     Vue | ||||
| ) { | ||||
|     function LADTableViewProvider(openmct) { | ||||
|         return { | ||||
|             key: 'LadTable', | ||||
|             name: 'LAD Table', | ||||
|             cssClass: 'icon-tabular-lad', | ||||
|             canView: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTable'; | ||||
|             }, | ||||
|             canEdit: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTable'; | ||||
|             }, | ||||
|             view: function (domainObject, objectPath) { | ||||
|                 let component; | ||||
| export default function LADTableViewProvider(openmct) { | ||||
|     return { | ||||
|         key: 'LadTable', | ||||
|         name: 'LAD Table', | ||||
|         cssClass: 'icon-tabular-lad', | ||||
|         canView: function (domainObject) { | ||||
|             return domainObject.type === 'LadTable'; | ||||
|         }, | ||||
|         canEdit: function (domainObject) { | ||||
|             return domainObject.type === 'LadTable'; | ||||
|         }, | ||||
|         view: function (domainObject, objectPath) { | ||||
|             let component; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component =  new Vue({ | ||||
|                             el: element, | ||||
|                             components: { | ||||
|                                 LadTableComponent: LadTableComponent.default | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             template: '<lad-table-component></lad-table-component>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     return LADTableViewProvider; | ||||
| }); | ||||
|             return { | ||||
|                 show: function (element) { | ||||
|                     component =  new Vue({ | ||||
|                         el: element, | ||||
|                         components: { | ||||
|                             LadTableComponent: LadTable | ||||
|                         }, | ||||
|                         provide: { | ||||
|                             openmct, | ||||
|                             domainObject, | ||||
|                             objectPath | ||||
|                         }, | ||||
|                         template: '<lad-table-component></lad-table-component>' | ||||
|                     }); | ||||
|                 }, | ||||
|                 destroy: function (element) { | ||||
|                     component.$destroy(); | ||||
|                     component = undefined; | ||||
|                 } | ||||
|             }; | ||||
|         }, | ||||
|         priority: function () { | ||||
|             return 1; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
|  | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * Open MCT, Copyright (c) 2014-2020, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
| @@ -24,10 +24,8 @@ | ||||
| <template> | ||||
| <tr @contextmenu.prevent="showContextMenu"> | ||||
|     <td>{{ name }}</td> | ||||
|     <td>{{ timestamp }}</td> | ||||
|     <td :class="valueClass"> | ||||
|         {{ value }} | ||||
|     </td> | ||||
|     <td>{{ formattedTimestamp }}</td> | ||||
|     <td :class="valueClass">{{ value }}</td> | ||||
| </tr> | ||||
| </template> | ||||
|  | ||||
| @@ -58,10 +56,16 @@ export default { | ||||
|             currentObjectPath | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         formattedTimestamp() { | ||||
|             return this.timestamp !== '---' ? this.formats[this.timestampKey].format(this.timestamp) : this.timestamp; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|         this.formats = this.openmct.telemetry.getFormatMap(this.metadata); | ||||
|         this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|         this.bounds = this.openmct.time.bounds(); | ||||
|  | ||||
|         this.limitEvaluator = this.openmct | ||||
|             .telemetry | ||||
| @@ -76,6 +80,7 @@ export default { | ||||
|             ); | ||||
|  | ||||
|         this.openmct.time.on('timeSystem', this.updateTimeSystem); | ||||
|         this.openmct.time.on('bounds', this.updateBounds); | ||||
|  | ||||
|         this.timestampKey = this.openmct.time.timeSystem().key; | ||||
|  | ||||
| @@ -89,43 +94,64 @@ export default { | ||||
|             .telemetry | ||||
|             .subscribe(this.domainObject, this.updateValues); | ||||
|  | ||||
|         this.openmct | ||||
|             .telemetry | ||||
|             .request(this.domainObject, {strategy: 'latest'}) | ||||
|             .then((array) => this.updateValues(array[array.length - 1])); | ||||
|         this.requestHistory(); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.stopWatchingMutation(); | ||||
|         this.unsubscribe(); | ||||
|         this.openmct.off('timeSystem', this.updateTimeSystem); | ||||
|         this.openmct.time.off('timeSystem', this.updateTimeSystem); | ||||
|         this.openmct.time.off('bounds', this.updateBounds); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateValues(datum) { | ||||
|             this.timestamp = this.formats[this.timestampKey].format(datum); | ||||
|             this.value = this.formats[this.valueKey].format(datum); | ||||
|             let newTimestamp = this.formats[this.timestampKey].parse(datum), | ||||
|                 shouldUpdate = this.timestamp === '---' || newTimestamp >= this.timestamp, | ||||
|                 limit; | ||||
|  | ||||
|             var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata); | ||||
|  | ||||
|             if (limit) { | ||||
|                 this.valueClass = limit.cssClass; | ||||
|             } else { | ||||
|                 this.valueClass = ''; | ||||
|             if(!this.inBounds(newTimestamp)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if(shouldUpdate) { | ||||
|                 this.timestamp = this.formats[this.timestampKey].parse(datum); | ||||
|                 this.value = this.formats[this.valueKey].format(datum); | ||||
|                 limit = this.limitEvaluator.evaluate(datum, this.valueMetadata); | ||||
|  | ||||
|                 if (limit) { | ||||
|                     this.valueClass = limit.cssClass; | ||||
|                 } else { | ||||
|                     this.valueClass = ''; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         requestHistory() { | ||||
|             this.timestamp = '---'; | ||||
|             this.openmct | ||||
|                 .telemetry | ||||
|                 .request(this.domainObject, { | ||||
|                     start: this.bounds.start, | ||||
|                     end: this.bounds.end, | ||||
|                     strategy: 'latest' | ||||
|                 }) | ||||
|                 .then((data) => this.updateValues(data[data.length - 1])); | ||||
|         }, | ||||
|         updateName(name) { | ||||
|             this.name = name; | ||||
|         }, | ||||
|         updateBounds(bounds, isTick) { | ||||
|             this.bounds = bounds; | ||||
|             if(!isTick) { | ||||
|                 this.requestHistory(); | ||||
|             } | ||||
|         }, | ||||
|         inBounds(timestamp) { | ||||
|             return timestamp >= this.bounds.start && timestamp <= this.bounds.end; | ||||
|         }, | ||||
|         updateTimeSystem(timeSystem) { | ||||
|             this.value = '---'; | ||||
|             this.timestamp = '---'; | ||||
|             this.valueClass = ''; | ||||
|             this.timestampKey = timeSystem.key; | ||||
|  | ||||
|             this.openmct | ||||
|                 .telemetry | ||||
|                 .request(this.domainObject, {strategy: 'latest'}) | ||||
|                 .then((array) => this.updateValues(array[array.length - 1])); | ||||
|  | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|   | ||||
| @@ -88,4 +88,3 @@ export default { | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -19,38 +19,36 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| import LADTableViewProvider from './LADTableViewProvider'; | ||||
| import LADTableSetViewProvider from './LADTableSetViewProvider'; | ||||
| import LADTableCompositionPolicy from './LADTableCompositionPolicy'; | ||||
|  | ||||
| define([ | ||||
|     './LADTableViewProvider', | ||||
|     './LADTableSetViewProvider' | ||||
| ], function ( | ||||
|     LADTableViewProvider, | ||||
|     LADTableSetViewProvider | ||||
| ) { | ||||
|     return function plugin() { | ||||
|         return function install(openmct) { | ||||
|             openmct.objectViews.addProvider(new LADTableViewProvider(openmct)); | ||||
|             openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct)); | ||||
| export default function plugin() { | ||||
|     return function install(openmct) { | ||||
|  | ||||
|             openmct.types.addType('LadTable', { | ||||
|                 name: "LAD Table", | ||||
|                 creatable: true, | ||||
|                 description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.", | ||||
|                 cssClass: 'icon-tabular-lad', | ||||
|                 initialize(domainObject) { | ||||
|                     domainObject.composition = []; | ||||
|                 } | ||||
|             }); | ||||
|         openmct.objectViews.addProvider(new LADTableViewProvider(openmct)); | ||||
|         openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct)); | ||||
|  | ||||
|             openmct.types.addType('LadTableSet', { | ||||
|                 name: "LAD Table Set", | ||||
|                 creatable: true, | ||||
|                 description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.", | ||||
|                 cssClass: 'icon-tabular-lad-set', | ||||
|                 initialize(domainObject) { | ||||
|                     domainObject.composition = []; | ||||
|                 } | ||||
|             }); | ||||
|         }; | ||||
|         openmct.types.addType('LadTable', { | ||||
|             name: "LAD Table", | ||||
|             creatable: true, | ||||
|             description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.", | ||||
|             cssClass: 'icon-tabular-lad', | ||||
|             initialize(domainObject) { | ||||
|                 domainObject.composition = []; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         openmct.types.addType('LadTableSet', { | ||||
|             name: "LAD Table Set", | ||||
|             creatable: true, | ||||
|             description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.", | ||||
|             cssClass: 'icon-tabular-lad-set', | ||||
|             initialize(domainObject) { | ||||
|                 domainObject.composition = []; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         openmct.composition.addPolicy(new LADTableCompositionPolicy(openmct)); | ||||
|     }; | ||||
| }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										356
									
								
								src/plugins/LADTable/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								src/plugins/LADTable/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,356 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, 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 LadPlugin from './plugin.js'; | ||||
| import Vue from 'vue'; | ||||
| import { | ||||
|     createOpenMct, | ||||
|     getMockObjects, | ||||
|     getMockTelemetry, | ||||
|     getLatestTelemetry | ||||
| } from 'testTools'; | ||||
|  | ||||
| let openmct, | ||||
|     ladPlugin, | ||||
|     parent, | ||||
|     child; | ||||
|  | ||||
| let selectors = {}; | ||||
| selectors.ladTableClass = '.c-table.c-lad-table'; | ||||
| selectors.ladTableBodyRows = selectors.ladTableClass + ' tbody tr'; | ||||
| selectors.ladTableBodyRowsFirstData = selectors.ladTableBodyRows + ' td:first-child'; | ||||
| selectors.ladTableBodyRowsSecondtData = selectors.ladTableBodyRows + ' td:nth-child(2)'; | ||||
| selectors.ladTableBodyRowsThirdData = selectors.ladTableBodyRows + ' td:nth-child(3)'; | ||||
| selectors.ladTableFirstBodyRow = selectors.ladTableClass + ' tbody tr:first-child'; | ||||
| selectors.ladTableFirstRowFirstData = selectors.ladTableBodyRows + ' td:first-child'; | ||||
| selectors.ladTableFirstRowSecondData = selectors.ladTableBodyRows + ' td:nth-child(2)'; | ||||
| selectors.ladTableFirstRowThirdData = selectors.ladTableBodyRows + ' td:nth-child(3)'; | ||||
|  | ||||
| selectors.ladTableSetTableHeaders = selectors.ladTableClass + ' .c-table__group-header'; | ||||
|  | ||||
| function utcTimeFormat(value) { | ||||
|     return new Date(value).toISOString().replace('T', ' ') | ||||
| } | ||||
|  | ||||
| describe("The LAD Table", () => { | ||||
|  | ||||
|     const ladTableKey = 'LadTable'; | ||||
|     let telemetryCount = 3, | ||||
|         timeFormat = 'utc', | ||||
|         mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }), | ||||
|         mockObj = getMockObjects({ | ||||
|             objectKeyStrings: ['ladTable', 'telemetry'], | ||||
|             format: timeFormat | ||||
|         }), | ||||
|         bounds = { | ||||
|             start: 0, | ||||
|             end: 4 | ||||
|         }; | ||||
|  | ||||
|     // add telemetry object as composition in lad table | ||||
|     mockObj.ladTable.composition.push(mockObj.telemetry.identifier); | ||||
|  | ||||
|     // this setups up the app | ||||
|     beforeEach((done) => { | ||||
|         const appHolder = document.createElement('div'); | ||||
|         appHolder.style.width = '640px'; | ||||
|         appHolder.style.height = '480px'; | ||||
|  | ||||
|         openmct = createOpenMct(); | ||||
|  | ||||
|         parent = document.createElement('div'); | ||||
|         child = document.createElement('div'); | ||||
|         parent.appendChild(child); | ||||
|  | ||||
|         spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([])); | ||||
|  | ||||
|         ladPlugin = new LadPlugin(); | ||||
|         openmct.install(ladPlugin); | ||||
|  | ||||
|         spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({})); | ||||
|  | ||||
|         openmct.time.bounds({ start: bounds.start, end: bounds.end }); | ||||
|  | ||||
|         openmct.on('start', done); | ||||
|         openmct.start(appHolder); | ||||
|     }); | ||||
|  | ||||
|     it("should provide a table view only for lad table objects", () => { | ||||
|         let applicableViews = openmct.objectViews.get(mockObj.ladTable), | ||||
|             ladTableView = applicableViews.find( | ||||
|                 (viewProvider) => viewProvider.key === ladTableKey | ||||
|             ); | ||||
|  | ||||
|         expect(applicableViews.length).toEqual(1); | ||||
|         expect(ladTableView).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     describe('composition', () => { | ||||
|         let ladTableCompositionCollection; | ||||
|  | ||||
|         beforeEach(() => { | ||||
|             ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable); | ||||
|             ladTableCompositionCollection.load(); | ||||
|         }); | ||||
|  | ||||
|         it("should accept telemetry producing objects", () => { | ||||
|             expect(() => { | ||||
|                 ladTableCompositionCollection.add(mockObj.telemetry); | ||||
|             }).not.toThrow(); | ||||
|         }); | ||||
|  | ||||
|         it("should reject non-telemtry producing objects", () => { | ||||
|             expect(()=> { | ||||
|                 ladTableCompositionCollection.add(mockObj.ladTable); | ||||
|             }).toThrow(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("table view", () => { | ||||
|         let applicableViews, | ||||
|             ladTableViewProvider, | ||||
|             ladTableView, | ||||
|             anotherTelemetryObj = getMockObjects({ | ||||
|                 objectKeyStrings: ['telemetry'], | ||||
|                 overwrite: { | ||||
|                     telemetry: { | ||||
|                         name: "New Telemetry Object", | ||||
|                         identifier: { namespace: "", key: "another-telemetry-object" } | ||||
|                     } | ||||
|                 } | ||||
|             }).telemetry; | ||||
|  | ||||
|         // add another telemetry object as composition in lad table to test multi rows | ||||
|         mockObj.ladTable.composition.push(anotherTelemetryObj.identifier); | ||||
|  | ||||
|         beforeEach(async () => { | ||||
|             let telemetryRequestResolve, | ||||
|                 telemetryObjectResolve, | ||||
|                 anotherTelemetryObjectResolve; | ||||
|             let telemetryRequestPromise = new Promise((resolve) => { | ||||
|                     telemetryRequestResolve = resolve; | ||||
|                 }), | ||||
|                 telemetryObjectPromise = new Promise((resolve) => { | ||||
|                     telemetryObjectResolve = resolve; | ||||
|                 }), | ||||
|                 anotherTelemetryObjectPromise = new Promise((resolve) => { | ||||
|                     anotherTelemetryObjectResolve = resolve; | ||||
|                 }) | ||||
|             openmct.telemetry.request.and.callFake(() => { | ||||
|                 telemetryRequestResolve(mockTelemetry); | ||||
|                 return telemetryRequestPromise; | ||||
|             }); | ||||
|             openmct.objects.get.and.callFake((obj) => { | ||||
|                 if(obj.key === 'telemetry-object') { | ||||
|                     telemetryObjectResolve(mockObj.telemetry); | ||||
|                     return telemetryObjectPromise; | ||||
|                 } else { | ||||
|                     anotherTelemetryObjectResolve(anotherTelemetryObj); | ||||
|                     return anotherTelemetryObjectPromise; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             openmct.time.bounds({ start: bounds.start, end: bounds.end }); | ||||
|  | ||||
|             applicableViews = openmct.objectViews.get(mockObj.ladTable); | ||||
|             ladTableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableKey); | ||||
|             ladTableView = ladTableViewProvider.view(mockObj.ladTable, [mockObj.ladTable]); | ||||
|             ladTableView.show(child, true); | ||||
|  | ||||
|             await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]); | ||||
|             return await Vue.nextTick(); | ||||
|         }); | ||||
|  | ||||
|         it("should show one row per object in the composition", () => { | ||||
|             const rowCount = parent.querySelectorAll(selectors.ladTableBodyRows).length; | ||||
|             expect(rowCount).toBe(mockObj.ladTable.composition.length); | ||||
|         }); | ||||
|  | ||||
|         it("should show the most recent datum from the telemetry producing object", async () => { | ||||
|             const latestDatum = getLatestTelemetry(mockTelemetry, { timeFormat }); | ||||
|             const expectedDate = utcTimeFormat(latestDatum[timeFormat]); | ||||
|             await Vue.nextTick(); | ||||
|             const latestDate = parent.querySelector(selectors.ladTableFirstRowSecondData).innerText; | ||||
|             expect(latestDate).toBe(expectedDate); | ||||
|         }); | ||||
|  | ||||
|         it("should show the name provided for the the telemetry producing object", () => { | ||||
|             const rowName = parent.querySelector(selectors.ladTableFirstRowFirstData).innerText, | ||||
|                 expectedName = mockObj.telemetry.name; | ||||
|             expect(rowName).toBe(expectedName); | ||||
|         }); | ||||
|  | ||||
|         it("should show the correct values for the datum based on domain and range hints", async () => { | ||||
|             const range = mockObj.telemetry.telemetry.values.find((val) => { | ||||
|                 return val.hints && val.hints.range !== undefined; | ||||
|             }).key; | ||||
|             const domain = mockObj.telemetry.telemetry.values.find((val) => { | ||||
|                 return val.hints && val.hints.domain !== undefined; | ||||
|             }).key; | ||||
|             const mostRecentTelemetry = getLatestTelemetry(mockTelemetry, { timeFormat }); | ||||
|             const rangeValue = mostRecentTelemetry[range]; | ||||
|             const domainValue = utcTimeFormat(mostRecentTelemetry[domain]); | ||||
|             await Vue.nextTick(); | ||||
|             const actualDomainValue = parent.querySelector(selectors.ladTableFirstRowSecondData).innerText; | ||||
|             const actualRangeValue = parent.querySelector(selectors.ladTableFirstRowThirdData).innerText; | ||||
|             expect(actualRangeValue).toBe(rangeValue); | ||||
|             expect(actualDomainValue).toBe(domainValue); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| describe("The LAD Table Set", () => { | ||||
|     const ladTableSetKey = 'LadTableSet'; | ||||
|     let telemetryCount = 3, | ||||
|         timeFormat = 'utc', | ||||
|         mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }), | ||||
|         mockObj = getMockObjects({ | ||||
|             objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry'] | ||||
|         }), | ||||
|         bounds = { | ||||
|             start: 0, | ||||
|             end: 4 | ||||
|         }; | ||||
|     // add mock telemetry to lad table and lad table to lad table set (composition) | ||||
|     mockObj.ladTable.composition.push(mockObj.telemetry.identifier); | ||||
|     mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier); | ||||
|  | ||||
|     beforeEach((done) => { | ||||
|         const appHolder = document.createElement('div'); | ||||
|         appHolder.style.width = '640px'; | ||||
|         appHolder.style.height = '480px'; | ||||
|  | ||||
|         openmct = createOpenMct(); | ||||
|  | ||||
|         parent = document.createElement('div'); | ||||
|         child = document.createElement('div'); | ||||
|         parent.appendChild(child); | ||||
|  | ||||
|         spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([])); | ||||
|  | ||||
|         ladPlugin = new LadPlugin(); | ||||
|         openmct.install(ladPlugin); | ||||
|  | ||||
|         spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({})); | ||||
|  | ||||
|         openmct.time.bounds({ start: bounds.start, end: bounds.end }); | ||||
|  | ||||
|         openmct.on('start', done); | ||||
|         openmct.start(appHolder); | ||||
|     }); | ||||
|  | ||||
|     it("should provide a lad table set view only for lad table set objects", () => { | ||||
|         let applicableViews = openmct.objectViews.get(mockObj.ladTableSet), | ||||
|             ladTableSetView = applicableViews.find( | ||||
|                 (viewProvider) => viewProvider.key === ladTableSetKey | ||||
|             ); | ||||
|  | ||||
|         expect(applicableViews.length).toEqual(1); | ||||
|         expect(ladTableSetView).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     describe('composition', () => { | ||||
|         let ladTableSetCompositionCollection; | ||||
|  | ||||
|         beforeEach(() => { | ||||
|             ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet); | ||||
|             ladTableSetCompositionCollection.load(); | ||||
|         }); | ||||
|  | ||||
|         it("should accept lad table objects", () => { | ||||
|             expect(() => { | ||||
|                 ladTableSetCompositionCollection.add(mockObj.ladTable); | ||||
|             }).not.toThrow(); | ||||
|         }); | ||||
|  | ||||
|         it("should reject non lad table objects", () => { | ||||
|             expect(()=> { | ||||
|                 ladTableSetCompositionCollection.add(mockObj.telemetry); | ||||
|             }).toThrow(); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("table view", () => { | ||||
|         let applicableViews, | ||||
|             ladTableSetViewProvider, | ||||
|             ladTableSetView, | ||||
|             otherObj = getMockObjects({ | ||||
|                 objectKeyStrings: ['ladTable'], | ||||
|                 overwrite: { | ||||
|                     ladTable: { | ||||
|                         name: "New LAD Table Object", | ||||
|                         identifier: { namespace: "", key: "another-lad-object" } | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         // add another lad table (with telemetry object) object to the lad table set for multi row test | ||||
|         otherObj.ladTable.composition.push(mockObj.telemetry.identifier); | ||||
|         mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier); | ||||
|  | ||||
|         beforeEach(async () => { | ||||
|             let telemetryRequestResolve, | ||||
|                 ladObjectResolve, | ||||
|                 anotherLadObjectResolve; | ||||
|             let telemetryRequestPromise = new Promise((resolve) => { | ||||
|                     telemetryRequestResolve = resolve; | ||||
|                 }), | ||||
|                 ladObjectPromise = new Promise((resolve) => { | ||||
|                     ladObjectResolve = resolve; | ||||
|                 }), | ||||
|                 anotherLadObjectPromise = new Promise((resolve) => { | ||||
|                     anotherLadObjectResolve = resolve; | ||||
|                 }) | ||||
|             openmct.telemetry.request.and.callFake(() => { | ||||
|                 telemetryRequestResolve(mockTelemetry); | ||||
|                 return telemetryRequestPromise; | ||||
|             }); | ||||
|             openmct.objects.get.and.callFake((obj) => { | ||||
|                 if(obj.key === 'lad-object') { | ||||
|                     ladObjectResolve(mockObj.ladObject); | ||||
|                     return ladObjectPromise; | ||||
|                 } else if(obj.key === 'another-lad-object') { | ||||
|                     anotherLadObjectResolve(otherObj.ladObject); | ||||
|                     return anotherLadObjectPromise; | ||||
|                 } | ||||
|  | ||||
|                 return Promise.resolve({}); | ||||
|             }); | ||||
|  | ||||
|             openmct.time.bounds({ start: bounds.start, end: bounds.end }); | ||||
|  | ||||
|             applicableViews = openmct.objectViews.get(mockObj.ladTableSet); | ||||
|             ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey); | ||||
|             ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]); | ||||
|             ladTableSetView.show(child, true); | ||||
|  | ||||
|             await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]); | ||||
|             return await Vue.nextTick(); | ||||
|         }); | ||||
|  | ||||
|         it("should show one row per lad table object in the composition", () => { | ||||
|             const rowCount = parent.querySelectorAll(selectors.ladTableSetTableHeaders).length; | ||||
|             expect(rowCount).toBe(mockObj.ladTableSet.composition.length); | ||||
|             pending(); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @@ -57,6 +57,7 @@ export default class ConditionManager extends EventEmitter { | ||||
|             endpoint, | ||||
|             this.telemetryReceived.bind(this, endpoint) | ||||
|         ); | ||||
|         // TODO check if this is needed | ||||
|         this.updateConditionTelemetry(); | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										72
									
								
								src/plugins/plotlyPlot/PlotlyViewProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/plugins/plotlyPlot/PlotlyViewProvider.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2019, 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 PlotlyViewLayout from './components/PlotlyViewLayout.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default function PlotlyViewProvider(openmct) { | ||||
|     return { | ||||
|         key: 'plotlyPlot', | ||||
|         name: 'Plotly Plot', | ||||
|         cssClass: 'icon-plot-overlay', | ||||
|         canView: function (domainObject) { | ||||
|             return domainObject.type === 'plotlyPlot'; | ||||
|         }, | ||||
|         canEdit: function (domainObject) { | ||||
|             return domainObject.type === 'plotlyPlot'; | ||||
|         }, | ||||
|         view: function (domainObject) { | ||||
|             let component; | ||||
|  | ||||
|             return { | ||||
|                 show: function (element, isEditing) { | ||||
|                     component =  new Vue({ | ||||
|                         provide: { | ||||
|                             openmct, | ||||
|                             domainObject | ||||
|                         }, | ||||
|                         el: element, | ||||
|                         components: { | ||||
|                             PlotlyViewLayout | ||||
|                         }, | ||||
|                         data() { | ||||
|                             return { | ||||
|                                 isEditing | ||||
|                             } | ||||
|                         }, | ||||
|                         template: '<plotly-view-layout :isEditing="isEditing"></plotly-view-layout>' | ||||
|                     }); | ||||
|                 }, | ||||
|                 onEditModeChange: function (isEditing) { | ||||
|                     component.isEditing = isEditing; | ||||
|                 }, | ||||
|                 destroy: function (element) { | ||||
|                     component.$destroy(); | ||||
|                     component = undefined; | ||||
|                 } | ||||
|             }; | ||||
|         }, | ||||
|         priority: function () { | ||||
|             return 1; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										221
									
								
								src/plugins/plotlyPlot/components/PlotlyViewLayout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/plugins/plotlyPlot/components/PlotlyViewLayout.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| <template> | ||||
| <div class="l-view-section"></div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Plotly from 'plotly.js-dist'; | ||||
| import moment from 'moment' | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     data: function () { | ||||
|  | ||||
|         return { | ||||
|             telemetryObjects: [], | ||||
|             bounds: this.openmct.time.bounds(), | ||||
|             timeRange:  0, | ||||
|             plotData: {}, | ||||
|             subscriptions: {} | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.plotElement = document.querySelector('.l-view-section'); | ||||
|  | ||||
|         this.composition = this.openmct.composition.get(this.domainObject); | ||||
|         this.composition.on('add', this.addTelemetry); | ||||
|         this.composition.on('remove', this.removeTelemetry); | ||||
|         this.composition.load(); | ||||
|  | ||||
|         this.openmct.time.on('bounds', this.refreshData); | ||||
|         this.openmct.time.on('clock', this.changeClock); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.unsubscribe(); | ||||
|     }, | ||||
|     methods: { | ||||
|         changeClock() { | ||||
|             if (this.openmct.time.clock()) { | ||||
|                 Plotly.purge(this.plotElement); | ||||
|                 this.telemetryObjects.forEach((telemetryObject, index) => { | ||||
|                     this.subscribeTo(telemetryObject, index); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         addTelemetry(telemetryObject) { | ||||
|             this.telemetryObjects.push(telemetryObject); | ||||
|             const index = this.telemetryObjects.findIndex(obj => obj === telemetryObject); | ||||
|             this.requestHistory(telemetryObject, index, true); | ||||
|             this.subscribeTo(telemetryObject, index); | ||||
|         }, | ||||
|         subscribeTo(telemetryObject, index) { | ||||
|             let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
|             this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => { | ||||
|                 //Check that telemetry object has not been removed since telemetry was requested. | ||||
|                 if (!this.telemetryObjects.includes(telemetryObject)) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 const length = this.plotData[telemetryObject.identifier.key].x.length; | ||||
|                 this.updateData(datum, index, length); | ||||
|             }); | ||||
|         }, | ||||
|         unsubscribe(keyString) { | ||||
|             this.subscriptions[keyString](); | ||||
|             delete this.subscriptions[keyString]; | ||||
|         }, | ||||
|         refreshData(bounds, isTick) { | ||||
|             this.bounds = bounds; | ||||
|  | ||||
|             this.telemetryObjects.forEach((telemetryObject, index) => { | ||||
|                 if(!isTick) { | ||||
|                     this.requestHistory(telemetryObject, index, false); | ||||
|                 } else { | ||||
|                     if (this.timeRange === 0 || this.timeRange !== this.bounds.end - this.bounds.start) { | ||||
|                         this.timeRange = this.bounds.end - this.bounds.start; | ||||
|  | ||||
|                         this.requestHistory(telemetryObject, index, false); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         requestHistory(telemetryObject, index, isAdd) { | ||||
|             this.openmct | ||||
|                 .telemetry | ||||
|                 .request(telemetryObject, { | ||||
|                     start: this.bounds.start, | ||||
|                     end: this.bounds.end | ||||
|                 }) | ||||
|                 .then((telemetryData) => { | ||||
|                     this.addTrace(telemetryData, telemetryObject, index, isAdd); | ||||
|                 }); | ||||
|         }, | ||||
|         getLayout(telemetryObject, isFixed) { | ||||
|             return { | ||||
|                 hovermode: 'compare', | ||||
|                 hoverdistance: -1, | ||||
|                 autosize: "true", | ||||
|                 showlegend: false, | ||||
|                 font: { | ||||
|                     family: "'Helvetica Neue', Helvetica, Arial, sans-serif", | ||||
|                     size: "12px", | ||||
|                     color: "#666" | ||||
|                 }, | ||||
|                 xaxis: { // hardcoded as UTC for now | ||||
|                     title: 'UTC', | ||||
|                     zeroline: false, | ||||
|                     range: isFixed ? 'undefined' : [ | ||||
|                         this.formatDatumX({utc: this.bounds.start}), | ||||
|                         this.formatDatumX({utc: this.bounds.start}) | ||||
|                     ] | ||||
|                 }, | ||||
|                 yaxis: { | ||||
|                     title: this.getYAxisLabel(telemetryObject), | ||||
|                     zeroline: false | ||||
|                 }, | ||||
|                 margin: { | ||||
|                     l: 40, | ||||
|                     r: 10, | ||||
|                     b: 40, | ||||
|                     t: 10 | ||||
|                 }, | ||||
|                 paper_bgcolor: 'transparent', | ||||
|                 plot_bgcolor: 'transparent' | ||||
|             } | ||||
|         }, | ||||
|         removeTelemetry(identifier) { | ||||
|             let keyString = this.openmct.objects.makeKeyString(identifier); | ||||
|             this.unsubscribe(keyString); | ||||
|             this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(identifier, object.identifier)); | ||||
|             if (!this.domainObject.composition.length) { | ||||
|                 Plotly.purge(this.plotElement); | ||||
|             } else { | ||||
|                 Plotly.deleteTraces(this.plotElement, this.domainObject.composition.length); | ||||
|             } | ||||
|         }, | ||||
|         getYAxisLabel(telemetryObject) { | ||||
|             this.setYAxisProp(telemetryObject); | ||||
|             const valueMetadatas = this.openmct.telemetry.getMetadata(telemetryObject).values(); | ||||
|             const index = valueMetadatas.findIndex(value => value.key === this.yAxisProp); | ||||
|             const yLabel = valueMetadatas[index].name; | ||||
|  | ||||
|             return yLabel; | ||||
|         }, | ||||
|         setYAxisProp(telemetryObject) { | ||||
|             if (telemetryObject.type === 'generator') { | ||||
|                 this.yAxisProp = 'sin'; | ||||
|             } else if (telemetryObject.type === 'example.state-generator') { | ||||
|                 this.yAxisProp = 'state'; | ||||
|             } else if (telemetryObject.type === 'conditionSet') { | ||||
|                 this.yAxisProp = 'output'; | ||||
|             } | ||||
|         }, | ||||
|         formatDatumX(datum) { | ||||
|             let timestamp = moment.utc(datum.utc).format('YYYY-MM-DDTHH:mm:ss[Z]'); | ||||
|             return timestamp; | ||||
|         }, | ||||
|         formatDatumY(datum) { | ||||
|             return datum.sin; | ||||
|         }, | ||||
|         addTrace(telemetryData, telemetryObject, index, isAdd) { | ||||
|             let x = []; | ||||
|             let y = []; | ||||
|  | ||||
|             const colors = ['red', 'green', 'blue']; | ||||
|  | ||||
|             telemetryData.forEach((datum) => { | ||||
|                 x.push(this.formatDatumX(datum)); | ||||
|                 y.push(this.formatDatumY(datum)); | ||||
|             }) | ||||
|  | ||||
|             let traceData = [{ // trace configuration | ||||
|                 x, | ||||
|                 y, | ||||
|                 type: 'scattergl', | ||||
|                 mode: 'lines+markers', | ||||
|                 line: { | ||||
|                     color: colors[index], // to set new color for each trace | ||||
|                     shape: 'linear' | ||||
|                 } | ||||
|             }]; | ||||
|  | ||||
|             this.plotData[telemetryObject.identifier.key] = traceData[0]; | ||||
|  | ||||
|             if (!this.plotElement.childNodes.length) { // not traces yet, so create new plot | ||||
|                 Plotly.newPlot( | ||||
|                     this.plotElement, | ||||
|                     traceData, | ||||
|                     this.getLayout(telemetryObject, true), | ||||
|                     { | ||||
|                         displayModeBar: false, // turns off hover-activated toolbar | ||||
|                         staticPlot: true // turns off hover effects on datapoints | ||||
|                     } | ||||
|                 ); | ||||
|             } else { | ||||
|                 if (isAdd) { // add a new trace to existing plot | ||||
|                     Plotly.addTraces(this.plotElement, traceData); | ||||
|                 } else { // update existing trace with new data (bounds change) | ||||
|                     Plotly.react(this.plotElement, Object.values(this.plotData), this.getLayout(telemetryObject, false)); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         updateData(datum, index, length) { | ||||
|             // plot all datapoints within bounds | ||||
|             if (datum.utc <= this.bounds.end && this.openmct.time.clock()) { | ||||
|                 Plotly.extendTraces( | ||||
|                     this.plotElement, | ||||
|                     { | ||||
|                         x: [[this.formatDatumX(datum)]], | ||||
|                         y: [[this.formatDatumY(datum)]] | ||||
|                     }, | ||||
|                     [index], // apply changes to particular trace | ||||
|                     length // set the fixed number of points (will drop points from beginning as new points are added) | ||||
|                 ); | ||||
|                 let newRange = { | ||||
|                     'xaxis.range': [this.formatDatumX({utc: this.bounds.start}),this.formatDatumX({utc: this.bounds.end})] | ||||
|                 }; | ||||
|                 Plotly.relayout(this.plotElement, newRange); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										2
									
								
								src/plugins/plotlyPlot/components/plotly.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/plugins/plotlyPlot/components/plotly.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| .plot svg { | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/plugins/plotlyPlot/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/plugins/plotlyPlot/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import PlotlyViewProvider from './PlotlyViewProvider.js'; | ||||
|  | ||||
| export default function () { | ||||
|     return function install(openmct) { | ||||
|         openmct.objectViews.addProvider(new PlotlyViewProvider(openmct)); | ||||
|  | ||||
|         openmct.types.addType('plotlyPlot', { | ||||
|             name: "Plotly Plot", | ||||
|             description: "Simple plot rendered by plotly.js", | ||||
|             creatable: true, | ||||
|             cssClass: 'icon-plot-overlay', | ||||
|             initialize: function (domainObject) { | ||||
|                 domainObject.composition = []; | ||||
|                 domainObject.telemetry = {}; | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
| } | ||||
| @@ -34,6 +34,7 @@ define([ | ||||
|     './URLIndicatorPlugin/URLIndicatorPlugin', | ||||
|     './telemetryMean/plugin', | ||||
|     './plot/plugin', | ||||
|     './plotlyPlot/plugin', | ||||
|     './telemetryTable/plugin', | ||||
|     './staticRootPlugin/plugin', | ||||
|     './notebook/plugin', | ||||
| @@ -66,6 +67,7 @@ define([ | ||||
|     URLIndicatorPlugin, | ||||
|     TelemetryMean, | ||||
|     PlotPlugin, | ||||
|     PlotlyPlotPlugin, | ||||
|     TelemetryTablePlugin, | ||||
|     StaticRootPlugin, | ||||
|     Notebook, | ||||
| @@ -171,8 +173,8 @@ define([ | ||||
|     plugins.ExampleImagery = ExampleImagery; | ||||
|     plugins.ImageryPlugin = ImageryPlugin; | ||||
|     plugins.Plot = PlotPlugin; | ||||
|     plugins.PlotlyPlot = PlotlyPlotPlugin.default; | ||||
|     plugins.TelemetryTable = TelemetryTablePlugin; | ||||
|  | ||||
|     plugins.SummaryWidget = SummaryWidget; | ||||
|     plugins.TelemetryMean = TelemetryMean; | ||||
|     plugins.URLIndicator = URLIndicatorPlugin; | ||||
| @@ -181,7 +183,7 @@ define([ | ||||
|     plugins.FolderView = FolderView; | ||||
|     plugins.Tabs = Tabs; | ||||
|     plugins.FlexibleLayout = FlexibleLayout; | ||||
|     plugins.LADTable = LADTable; | ||||
|     plugins.LADTable = LADTable.default; | ||||
|     plugins.Filters = Filters; | ||||
|     plugins.ObjectMigration = ObjectMigration.default; | ||||
|     plugins.GoToOriginalAction = GoToOriginalAction.default; | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
| @import "../plugins/folderView/components/list-item.scss"; | ||||
| @import "../plugins/folderView/components/list-view.scss"; | ||||
| @import "../plugins/imagery/components/imagery-view-layout.scss"; | ||||
| @import "../plugins/plotlyPlot/components/plotly.scss"; | ||||
| @import "../plugins/telemetryTable/components/table-row.scss"; | ||||
| @import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss"; | ||||
| @import "../plugins/tabs/components/tabs.scss"; | ||||
|   | ||||
							
								
								
									
										224
									
								
								src/testTools.js
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								src/testTools.js
									
									
									
									
									
								
							| @@ -1,4 +1,6 @@ | ||||
| import MCT from 'MCT'; | ||||
| let nativeFunctions = [], | ||||
|     mockObjects = setMockObjects(); | ||||
|  | ||||
| export function createOpenMct() { | ||||
|     const openmct = new MCT(); | ||||
| @@ -16,3 +18,225 @@ export function createMouseEvent(eventName) { | ||||
|         view: window | ||||
|     }); | ||||
| } | ||||
|  | ||||
| export const spyOnBuiltins = (functionNames, object = window) => { | ||||
|     functionNames.forEach(functionName => { | ||||
|         if (nativeFunctions[functionName]) { | ||||
|             throw `Builtin spy function already defined for ${functionName}`; | ||||
|         } | ||||
|  | ||||
|         nativeFunctions.push({functionName, object, nativeFunction: object[functionName]}); | ||||
|         spyOn(object, functionName); | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| export const clearBuiltinSpies = () => { | ||||
|     nativeFunctions.forEach(clearBuiltinSpy); | ||||
|     nativeFunctions = []; | ||||
| }; | ||||
|  | ||||
| function clearBuiltinSpy(funcDefinition) { | ||||
|     funcDefinition.object[funcDefinition.functionName] = funcDefinition.nativeFunction; | ||||
| } | ||||
|  | ||||
| export const getLatestTelemetry = (telemetry = [], opts = {}) => { | ||||
|     let latest = [], | ||||
|         timeFormat = opts.timeFormat || 'utc'; | ||||
|  | ||||
|     if(telemetry.length) { | ||||
|         latest = telemetry.reduce((prev, cur) => { | ||||
|             return prev[timeFormat] > cur[timeFormat] ? prev : cur; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return latest; | ||||
| }; | ||||
|  | ||||
| // EXAMPLE: | ||||
| // getMockObjects({ | ||||
| //     name: 'Jamie Telemetry', | ||||
| //     keys: ['test','other','yeah','sup'], | ||||
| //     format: 'local', | ||||
| //     telemetryConfig: { | ||||
| //          hints: { | ||||
| //              test: { | ||||
| //                  domain: 1 | ||||
| //              }, | ||||
| //              other: { | ||||
| //                  range: 2 | ||||
| //              } | ||||
| //          } | ||||
| //      } | ||||
| // }) | ||||
| export const getMockObjects = (opts = {}) => { | ||||
|     opts.type = opts.type || 'default'; | ||||
|     if(opts.objectKeyStrings && !Array.isArray(opts.objectKeyStrings)) { | ||||
|         throw `"getMockObjects" optional parameter "objectKeyStrings" must be an array of string object keys`; | ||||
|     } | ||||
|  | ||||
|     let requestedMocks = {}; | ||||
|  | ||||
|     if (!opts.objectKeyStrings) { | ||||
|         requestedMocks = copyObj(mockObjects[opts.type]); | ||||
|     } else { | ||||
|         opts.objectKeyStrings.forEach(objKey => { | ||||
|             if(mockObjects[opts.type] && mockObjects[opts.type][objKey]) { | ||||
|                 requestedMocks[objKey] = copyObj(mockObjects[opts.type][objKey]); | ||||
|             } else { | ||||
|                 throw `No mock object for object key "${objKey}" of type "${opts.type}"`; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // build out custom telemetry mappings if necessary | ||||
|     if(requestedMocks.telemetry && opts.telemetryConfig) { | ||||
|         let keys = opts.telemetryConfig.keys, | ||||
|             format = opts.telemetryConfig.format || 'utc', | ||||
|             hints = opts.telemetryConfig.hints, | ||||
|             values; | ||||
|  | ||||
|         // if utc, keep default | ||||
|         if(format === 'utc') { | ||||
|             // save for later if new keys | ||||
|             if(keys) { | ||||
|                 format = requestedMocks.telemetry | ||||
|                     .telemetry.values.find((vals) => vals.key === 'utc'); | ||||
|             } | ||||
|         } else { | ||||
|             format = { | ||||
|                 key: format, | ||||
|                 name: "Time", | ||||
|                 format: format === 'local' ? 'local-format' : format, | ||||
|                 hints: { | ||||
|                     domain: 1 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if(keys) { | ||||
|             values = keys.map((key) => ({ key, name: key + ' attribute' })); | ||||
|             values.push(format); // add time format back in | ||||
|         } else { | ||||
|             values = requestedMocks.telemetry.telemetry.values; | ||||
|         } | ||||
|  | ||||
|         if(hints) { | ||||
|             for(let val of values) { | ||||
|                 if(hints[val.key]) { | ||||
|                     val.hints = hints[val.key]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         requestedMocks.telemetry.telemetry.values = values; | ||||
|     } | ||||
|  | ||||
|     // overwrite any field keys | ||||
|     if(opts.overwrite) { | ||||
|         for(let mock in requestedMocks) { | ||||
|             if(opts.overwrite[mock]) { | ||||
|                 for(let key in opts.overwrite[mock]) { | ||||
|                     if (Object.prototype.hasOwnProperty.call(opts.overwrite[mock], key)) { | ||||
|                         requestedMocks[mock][key] = opts.overwrite[mock][key]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return requestedMocks; | ||||
| } | ||||
|  | ||||
| // EXAMPLE: | ||||
| // getMockTelemetry({ | ||||
| //     name: 'My Telemetry', | ||||
| //     keys: ['test','other','yeah','sup'], | ||||
| //     count: 8, | ||||
| //     format: 'local' | ||||
| // }) | ||||
| export const getMockTelemetry = (opts = {}) => { | ||||
|     let count = opts.count || 2, | ||||
|         format = opts.format || 'utc', | ||||
|         name = opts.name || 'Mock Telemetry Datum', | ||||
|         keyCount = 2, | ||||
|         keys = false, | ||||
|         telemetry = []; | ||||
|  | ||||
|     if(opts.keys && Array.isArray(opts.keys)) { | ||||
|         keyCount = opts.keys.length; | ||||
|         keys = opts.keys; | ||||
|     } else if(opts.keyCount) { | ||||
|         keyCount = opts.keyCount; | ||||
|     } | ||||
|  | ||||
|     for(let i = 1; i < count + 1; i++) { | ||||
|         let datum = { | ||||
|             [format]: i, | ||||
|             name | ||||
|         } | ||||
|  | ||||
|         for(let k = 1; k < keyCount + 1; k++) { | ||||
|             let key = keys ? keys[k - 1] : 'some-key-' + k, | ||||
|                 value = keys ? keys[k - 1] + ' value ' + i : 'some value ' + i + '-' + k; | ||||
|             datum[key] = value; | ||||
|         } | ||||
|  | ||||
|         telemetry.push(datum); | ||||
|     } | ||||
|  | ||||
|     return telemetry; | ||||
| } | ||||
|  | ||||
| // copy objects a bit more easily | ||||
| function copyObj(obj) { | ||||
|     return JSON.parse(JSON.stringify(obj)); | ||||
| } | ||||
|  | ||||
| // add any other necessary types to this mockObjects object | ||||
| function setMockObjects() { | ||||
|     return { | ||||
|         default: { | ||||
|             ladTable: { | ||||
|                 identifier: { namespace: "", key: "lad-object"}, | ||||
|                 type: 'LadTable', | ||||
|                 composition: [] | ||||
|             }, | ||||
|             ladTableSet: { | ||||
|                 identifier: { namespace: "", key: "lad-set-object"}, | ||||
|                 type: 'LadTableSet', | ||||
|                 composition: [] | ||||
|             }, | ||||
|             telemetry: { | ||||
|                 identifier: { namespace: "", key: "telemetry-object"}, | ||||
|                 type: "test-telemetry-object", | ||||
|                 name: "Test Telemetry Object", | ||||
|                 telemetry: { | ||||
|                     values: [{ | ||||
|                         key: "name", | ||||
|                         name: "Name", | ||||
|                         format: "string" | ||||
|                     },{ | ||||
|                         key: "utc", | ||||
|                         name: "Time", | ||||
|                         format: "utc", | ||||
|                         hints: { | ||||
|                             domain: 1 | ||||
|                         } | ||||
|                     },{ | ||||
|                         name: "Some attribute 1", | ||||
|                         key: "some-key-1", | ||||
|                         hints: { | ||||
|                             range: 1 | ||||
|                         } | ||||
|                     }, { | ||||
|                         name: "Some attribute 2", | ||||
|                         key: "some-key-2" | ||||
|                     }] | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         otherType: { | ||||
|             example: {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user