Compare commits
	
		
			15 Commits
		
	
	
		
			two-oh-two
			...
			api-tutori
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a0de23091b | ||
|   | 722e384df4 | ||
|   | ab83beb17c | ||
|   | 370421b4a5 | ||
|   | 3e5f4c8b88 | ||
|   | b1f1568657 | ||
|   | 33d24c9b60 | ||
|   | 33c060473a | ||
|   | 3b01b9ee51 | ||
|   | 26a0aed159 | ||
|   | 1c4a67ebd1 | ||
|   | f6fd572e4f | ||
|   | 2ec1f76fc5 | ||
|   | 8f618a1f35 | ||
|   | 6a89e6da50 | 
| @@ -320,7 +320,7 @@ define([ | ||||
| +          { | ||||
| +              "key": "example.todo", | ||||
| +              "name": "To-Do List", | ||||
| +              "cssclass": "icon-check", | ||||
| +              "cssClass": "icon-check", | ||||
| +              "description": "A list of things that need to be done.", | ||||
| +              "features": ["creation"] | ||||
| +          } | ||||
| @@ -340,7 +340,7 @@ Going through the properties we've defined: | ||||
| domain objects of this type. | ||||
| * The `name` of "To-Do List" is the human-readable name for this type, and will  | ||||
| be shown to users. | ||||
| * The `cssclass` maps to an icon that will be shown for each To-Do List. The icons  | ||||
| * The `cssClass` maps to an icon that will be shown for each To-Do List. The icons  | ||||
| are defined in our [custom open MCT icon set](https://github.com/nasa/openmct/blob/master/platform/commonUI/general/res/sass/_glyphs.scss).  | ||||
| A complete list of available icons will be provided in the future. | ||||
| * The `description` is also human-readable, and will be used whenever a longer  | ||||
| @@ -416,7 +416,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"] | ||||
|             } | ||||
| @@ -425,7 +425,7 @@ define([ | ||||
| +           { | ||||
| +               "key": "example.todo", | ||||
| +               "type": "example.todo", | ||||
| +               "cssclass": "icon-check", | ||||
| +               "cssClass": "icon-check", | ||||
| +               "name": "List", | ||||
| +               "templateUrl": "templates/todo.html", | ||||
| +               "editable": true | ||||
| @@ -447,7 +447,7 @@ the domain object type, but could have chosen any unique name. | ||||
| domain objects of that type. This means that we'll see this view for To-do Lists  | ||||
| that we create, but not for other domain objects (such as Folders.) | ||||
|  | ||||
| * The `cssclass` and `name` properties describe the icon and human-readable name  | ||||
| * The `cssClass` and `name` properties describe the icon and human-readable name  | ||||
| for this view to display in the UI where needed (if multiple views are available  | ||||
| for To-do Lists, the user will be able to choose one.) | ||||
|  | ||||
| @@ -473,7 +473,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"], | ||||
| +               "model": { | ||||
| @@ -488,7 +488,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "type": "example.todo", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "name": "List", | ||||
|                 "templateUrl": "templates/todo.html", | ||||
|                 "editable": true | ||||
| @@ -647,7 +647,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"], | ||||
|                 "model": { | ||||
| @@ -662,7 +662,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "type": "example.todo", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "name": "List", | ||||
|                 "templateUrl": "templates/todo.html", | ||||
|                 "editable": true | ||||
| @@ -741,7 +741,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"], | ||||
|                 "model": { | ||||
| @@ -756,7 +756,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "type": "example.todo", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "name": "List", | ||||
|                 "templateUrl": "templates/todo.html", | ||||
|                 "editable": true, | ||||
| @@ -766,7 +766,7 @@ define([ | ||||
| +                           "items": [ | ||||
| +                               { | ||||
| +                                   "text": "Add Task", | ||||
| +                                   "cssclass": "icon-plus", | ||||
| +                                   "cssClass": "icon-plus", | ||||
| +                                   "method": "addTask", | ||||
| +                                   "control": "button" | ||||
| +                               } | ||||
| @@ -775,7 +775,7 @@ define([ | ||||
| +                       { | ||||
| +                           "items": [ | ||||
| +                               { | ||||
| +                                   "cssclass": "icon-trash", | ||||
| +                                   "cssClass": "icon-trash", | ||||
| +                                   "method": "removeTask", | ||||
| +                                   "control": "button" | ||||
| +                               } | ||||
| @@ -971,7 +971,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"], | ||||
|                 "model": { | ||||
| @@ -986,7 +986,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "type": "example.todo", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "name": "List", | ||||
|                 "templateUrl": "templates/todo.html", | ||||
|                 "editable": true, | ||||
| @@ -996,7 +996,7 @@ define([ | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "text": "Add Task", | ||||
|                                     "cssclass": "icon-plus", | ||||
|                                     "cssClass": "icon-plus", | ||||
|                                     "method": "addTask", | ||||
|                                     "control": "button" | ||||
|                                 } | ||||
| @@ -1005,7 +1005,7 @@ define([ | ||||
|                         { | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "cssclass": "icon-trash", | ||||
|                                     "cssClass": "icon-trash", | ||||
|                                     "method": "removeTask", | ||||
|                                     "control": "button" | ||||
|                                 } | ||||
| @@ -1236,7 +1236,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "name": "To-Do List", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "description": "A list of things that need to be done.", | ||||
|                 "features": ["creation"], | ||||
|                 "model": { | ||||
| @@ -1248,7 +1248,7 @@ define([ | ||||
|             { | ||||
|                 "key": "example.todo", | ||||
|                 "type": "example.todo", | ||||
|                 "cssclass": "icon-check", | ||||
|                 "cssClass": "icon-check", | ||||
|                 "name": "List", | ||||
|                 "templateUrl": "templates/todo.html", | ||||
|                 "editable": true, | ||||
| @@ -1258,7 +1258,7 @@ define([ | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "text": "Add Task", | ||||
|                                     "cssclass": "icon-plus", | ||||
|                                     "cssClass": "icon-plus", | ||||
|                                     "method": "addTask", | ||||
|                                     "control": "button" | ||||
|                                 } | ||||
| @@ -1267,7 +1267,7 @@ define([ | ||||
|                         { | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "cssclass": "icon-trash", | ||||
|                                     "cssClass": "icon-trash", | ||||
|                                     "method": "removeTask", | ||||
|                                     "control": "button" | ||||
|                                 } | ||||
| @@ -1374,7 +1374,7 @@ define([ | ||||
|             { | ||||
|                 "name": "Bar Graph", | ||||
|                 "key": "example.bargraph", | ||||
|                 "cssclass": "icon-autoflow-tabular", | ||||
|                 "cssClass": "icon-autoflow-tabular", | ||||
|                 "templateUrl": "templates/bargraph.html", | ||||
|                 "needs": [ "telemetry" ], | ||||
|                 "delegation": true | ||||
| @@ -1677,7 +1677,7 @@ define([ | ||||
|             { | ||||
|                 "name": "Bar Graph", | ||||
|                 "key": "example.bargraph", | ||||
|                 "cssclass": "icon-autoflow-tabular", | ||||
|                 "cssClass": "icon-autoflow-tabular", | ||||
|                 "templateUrl": "templates/bargraph.html", | ||||
|                 "needs": [ "telemetry" ], | ||||
|                 "delegation": true | ||||
| @@ -1843,7 +1843,7 @@ define([ | ||||
|             { | ||||
|                 "name": "Bar Graph", | ||||
|                 "key": "example.bargraph", | ||||
|                 "cssclass": "icon-autoflow-tabular", | ||||
|                 "cssClass": "icon-autoflow-tabular", | ||||
|                 "templateUrl": "templates/bargraph.html", | ||||
|                 "needs": [ "telemetry" ], | ||||
|                 "delegation": true, | ||||
| @@ -2320,7 +2320,7 @@ define([ | ||||
|                 { | ||||
|                     "name": "Spacecraft", | ||||
|                     "key": "example.spacecraft", | ||||
|                     "cssclass": "icon-object" | ||||
|                     "cssClass": "icon-object" | ||||
|                 } | ||||
|             ], | ||||
|             "roots": [ | ||||
| @@ -2706,18 +2706,18 @@ define([ | ||||
|             { | ||||
|                 "name": "Spacecraft", | ||||
|                 "key": "example.spacecraft", | ||||
|                 "cssclass": "icon-object" | ||||
|                 "cssClass": "icon-object" | ||||
|             }, | ||||
| +           { | ||||
| +               "name": "Subsystem", | ||||
| +               "key": "example.subsystem", | ||||
| +               "cssclass": "icon-object", | ||||
| +               "cssClass": "icon-object", | ||||
| +               "model": { "composition": [] } | ||||
| +           }, | ||||
| +           { | ||||
| +               "name": "Measurement", | ||||
| +               "key": "example.measurement", | ||||
| +               "cssclass": "icon-telemetry", | ||||
| +               "cssClass": "icon-telemetry", | ||||
| +               "model": { "telemetry": {} }, | ||||
| +               "telemetry": { | ||||
| +                   "source": "example.source", | ||||
| @@ -3031,18 +3031,18 @@ define([ | ||||
|             { | ||||
|                 "name": "Spacecraft", | ||||
|                 "key": "example.spacecraft", | ||||
|                 "cssclass": "icon-object" | ||||
|                 "cssClass": "icon-object" | ||||
|             }, | ||||
|             { | ||||
|                 "name": "Subsystem", | ||||
|                 "key": "example.subsystem", | ||||
|                 "cssclass": "icon-object", | ||||
|                 "cssClass": "icon-object", | ||||
|                 "model": { "composition": [] } | ||||
|             }, | ||||
|             { | ||||
|                 "name": "Measurement", | ||||
|                 "key": "example.measurement", | ||||
|                 "cssclass": "icon-telemetry", | ||||
|                 "cssClass": "icon-telemetry", | ||||
|                 "model": { "telemetry": {} }, | ||||
|                 "telemetry": { | ||||
|                     "source": "example.source", | ||||
|   | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "eventGenerator", | ||||
|                     "name": "Event Message Generator", | ||||
|                     "cssclass": "icon-folder-new", | ||||
|                     "cssClass": "icon-folder-new", | ||||
|                     "description": "For development use. Creates sample event message data that mimics a live data stream.", | ||||
|                     "priority": 10, | ||||
|                     "features": "creation", | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|                     "name": "Export Telemetry as CSV", | ||||
|                     "implementation": ExportTelemetryAsCSVAction, | ||||
|                     "category": "contextual", | ||||
|                     "cssclass": "icon-download", | ||||
|                     "cssClass": "icon-download", | ||||
|                     "depends": [ "exportService" ] | ||||
|                 } | ||||
|             ] | ||||
|   | ||||
| @@ -1,183 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
| /*global define*/ | ||||
|  | ||||
| define([ | ||||
|     "./src/SinewaveTelemetryProvider", | ||||
|     "./src/SinewaveLimitCapability", | ||||
|     "./src/SinewaveDeltaFormat", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     SinewaveTelemetryProvider, | ||||
|     SinewaveLimitCapability, | ||||
|     SinewaveDeltaFormat, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     "use strict"; | ||||
|  | ||||
|     legacyRegistry.register("example/generator", { | ||||
|         "name": "Sine Wave Generator", | ||||
|         "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", | ||||
|         "extensions": { | ||||
|             "components": [ | ||||
|                 { | ||||
|                     "implementation": SinewaveTelemetryProvider, | ||||
|                     "type": "provider", | ||||
|                     "provides": "telemetryService", | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "$timeout" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "capabilities": [ | ||||
|                 { | ||||
|                     "key": "limit", | ||||
|                     "implementation": SinewaveLimitCapability | ||||
|                 } | ||||
|             ], | ||||
|             "formats": [ | ||||
|                 { | ||||
|                     "key": "example.delta", | ||||
|                     "implementation": SinewaveDeltaFormat | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "TIME_CONDUCTOR_DOMAINS", | ||||
|                     "value": [ | ||||
|                         { | ||||
|                             "key": "time", | ||||
|                             "name": "Time" | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "yesterday", | ||||
|                             "name": "Yesterday" | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "delta", | ||||
|                             "name": "Delta", | ||||
|                             "format": "example.delta" | ||||
|                         } | ||||
|                     ], | ||||
|                     "priority": -1 | ||||
|                 } | ||||
|             ], | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "key": "generator", | ||||
|                     "name": "Sine Wave Generator", | ||||
|                     "cssclass": "icon-telemetry", | ||||
|                     "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", | ||||
|                     "priority": 10, | ||||
|                     "features": "creation", | ||||
|                     "model": { | ||||
|                         "telemetry": { | ||||
|                             "period": 10, | ||||
|                             "amplitude": 1, | ||||
|                             "offset": 0, | ||||
|                             "dataRateInHz": 1 | ||||
|                         } | ||||
|                     }, | ||||
|                     "telemetry": { | ||||
|                         "source": "generator", | ||||
|                         "domains": [ | ||||
|                             { | ||||
|                                 "key": "utc", | ||||
|                                 "name": "Time" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "yesterday", | ||||
|                                 "name": "Yesterday" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "delta", | ||||
|                                 "name": "Delta", | ||||
|                                 "format": "example.delta" | ||||
|                             } | ||||
|                         ], | ||||
|                         "ranges": [ | ||||
|                             { | ||||
|                                 "key": "sin", | ||||
|                                 "name": "Sine" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "cos", | ||||
|                                 "name": "Cosine" | ||||
|                             } | ||||
|                         ] | ||||
|                     }, | ||||
|                     "properties": [ | ||||
|                         { | ||||
|                             "name": "Period", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "period", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|                                 "telemetry", | ||||
|                                 "period" | ||||
|                             ], | ||||
|                             "pattern": "^\\d*(\\.\\d*)?$" | ||||
|                         }, | ||||
|                         { | ||||
|                             "name": "Amplitude", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "amplitude", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|                                 "telemetry", | ||||
|                                 "amplitude" | ||||
|                             ], | ||||
|                             "pattern": "^\\d*(\\.\\d*)?$" | ||||
|                         }, | ||||
|                         { | ||||
|                             "name": "Offset", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "offset", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|                                 "telemetry", | ||||
|                                 "offset" | ||||
|                             ], | ||||
|                             "pattern": "^\\d*(\\.\\d*)?$" | ||||
|                         }, | ||||
|                         { | ||||
|                             "name": "Data Rate (hz)", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "dataRateInHz", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|                                 "telemetry", | ||||
|                                 "dataRateInHz" | ||||
|                             ], | ||||
|                             "pattern": "^\\d*(\\.\\d*)?$" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "imagery", | ||||
|                     "name": "Example Imagery", | ||||
|                     "cssclass": "icon-image", | ||||
|                     "cssClass": "icon-image", | ||||
|                     "features": "creation", | ||||
|                     "description": "For development use. Creates example imagery data that mimics a live imagery stream.", | ||||
|                     "priority": 10, | ||||
|   | ||||
| @@ -31,7 +31,7 @@ define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'], | ||||
|         this.metadata = { | ||||
|             key: 'test-lad', | ||||
|             mode: 'lad', | ||||
|             cssclass: 'icon-clock', | ||||
|             cssClass: 'icon-clock', | ||||
|             label: 'Latest Available Data', | ||||
|             name: 'Latest available data', | ||||
|             description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.' | ||||
|   | ||||
| @@ -41,18 +41,18 @@ define([ | ||||
|                 { | ||||
|                     "name":"Mars Science Laboratory", | ||||
|                     "key": "msl.curiosity", | ||||
|                     "cssclass": "icon-object" | ||||
|                     "cssClass": "icon-object" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Instrument", | ||||
|                     "key": "msl.instrument", | ||||
|                     "cssclass": "icon-object", | ||||
|                     "cssClass": "icon-object", | ||||
|                     "model": {"composition": []} | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Measurement", | ||||
|                     "key": "msl.measurement", | ||||
|                     "cssclass": "icon-telemetry", | ||||
|                     "cssClass": "icon-telemetry", | ||||
|                     "model": {"telemetry": {}}, | ||||
|                     "telemetry": { | ||||
|                         "source": "rems.source", | ||||
|   | ||||
| @@ -81,7 +81,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "plot", | ||||
|                     "name": "Example Telemetry Plot", | ||||
|                     "cssclass": "icon-telemetry-panel", | ||||
|                     "cssClass": "icon-telemetry-panel", | ||||
|                     "description": "For development use. A plot for displaying telemetry.", | ||||
|                     "priority": 10, | ||||
|                     "delegates": [ | ||||
| @@ -129,7 +129,7 @@ define([ | ||||
|                         { | ||||
|                             "name": "Period", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "cssClass": "l-input-sm l-numeric", | ||||
|                             "key": "period", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|   | ||||
| @@ -63,7 +63,7 @@ define( | ||||
|                  * Get the CSS class that defines the icon | ||||
|                  * to display in this indicator. This will appear | ||||
|                  * as a dataflow icon. | ||||
|                  * @returns {string} the cssclass of the dataflow icon | ||||
|                  * @returns {string} the cssClass of the dataflow icon | ||||
|                  */ | ||||
|                 getCssClass: function () { | ||||
|                     return "icon-connectivity"; | ||||
|   | ||||
| @@ -69,6 +69,11 @@ var gulp = require('gulp'), | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| if (process.env.NODE_ENV === 'development') { | ||||
|     options.requirejsOptimize.optimize = 'none'; | ||||
| } | ||||
|  | ||||
|  | ||||
| gulp.task('scripts', function () { | ||||
|     var requirejsOptimize = require('gulp-requirejs-optimize'); | ||||
|     var replace = require('gulp-replace-task'); | ||||
|   | ||||
							
								
								
									
										14
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								index.html
									
									
									
									
									
								
							| @@ -28,17 +28,25 @@ | ||||
|     <script src="bower_components/requirejs/require.js"> | ||||
|     </script> | ||||
|     <script> | ||||
|         require(['openmct'], function (openmct) { | ||||
|         require(['openmct'], function (openmct, generatorPlugin) { | ||||
|             [ | ||||
|                 'example/imagery', | ||||
|                 'example/eventGenerator', | ||||
|                 'example/generator' | ||||
|                 'example/eventGenerator' | ||||
|             ].forEach( | ||||
|                 openmct.legacyRegistry.enable.bind(openmct.legacyRegistry) | ||||
|             ); | ||||
|             openmct.install(openmct.plugins.myItems); | ||||
|             openmct.install(openmct.plugins.localStorage); | ||||
|             openmct.install(openmct.plugins.espresso); | ||||
|  | ||||
|             openmct.install(openmct.plugins.Generator()); | ||||
|             openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|             openmct.install(openmct.plugins.Conductor({ | ||||
|                 defaultTimeSystem: 'utc', | ||||
|                 defaultTimespan: 30 * 60 * 1000, | ||||
|                 showConductor: true | ||||
|             })); | ||||
|  | ||||
|             openmct.start(); | ||||
|         }); | ||||
|     </script> | ||||
|   | ||||
| @@ -226,7 +226,7 @@ define([ | ||||
|                         "$window" | ||||
|                     ], | ||||
|                     "group": "windowing", | ||||
|                     "cssclass": "icon-new-window", | ||||
|                     "cssClass": "icon-new-window", | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 { | ||||
| @@ -241,7 +241,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "items", | ||||
|                     "name": "Items", | ||||
|                     "cssclass": "icon-thumbs-strip", | ||||
|                     "cssClass": "icon-thumbs-strip", | ||||
|                     "description": "Grid of available items", | ||||
|                     "template": itemsTemplate, | ||||
|                     "uses": [ | ||||
|   | ||||
| @@ -43,24 +43,24 @@ define([], function () { | ||||
|             return context.getParent(); | ||||
|         } | ||||
|  | ||||
|         function isOrphan(domainObject) { | ||||
|             var parent = getParent(domainObject), | ||||
|                 composition = parent.getModel().composition, | ||||
|                 id = domainObject.getId(); | ||||
|             return !composition || (composition.indexOf(id) === -1); | ||||
|         } | ||||
|  | ||||
|         function navigateToParent(domainObject) { | ||||
|         function preventOrphanNavigation(domainObject) { | ||||
|             var parent = getParent(domainObject); | ||||
|             return parent.getCapability('action').perform('navigate'); | ||||
|             parent.useCapability('composition') | ||||
|                 .then(function (composees) { | ||||
|                     var isOrphan = composees.every(function (c) { | ||||
|                         return c.getId() !== domainObject.getId() | ||||
|                     }); | ||||
|                     if (isOrphan) { | ||||
|                         parent.getCapability('action').perform('navigate'); | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|  | ||||
|         function checkNavigation() { | ||||
|             var navigatedObject = navigationService.getNavigation(); | ||||
|             if (navigatedObject.hasCapability('context') && | ||||
|                 isOrphan(navigatedObject)) { | ||||
|             if (navigatedObject.hasCapability('context')) { | ||||
|                 if (!navigatedObject.getCapability('editor').isEditContextRoot()) { | ||||
|                     navigateToParent(navigatedObject); | ||||
|                     preventOrphanNavigation(navigatedObject); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -46,12 +46,12 @@ define( | ||||
|         }; | ||||
|  | ||||
|         FullscreenAction.prototype.getMetadata = function () { | ||||
|             // We override getMetadata, because the icon cssclass and | ||||
|             // We override getMetadata, because the icon cssClass and | ||||
|             // description need to be determined at run-time | ||||
|             // based on whether or not we are currently | ||||
|             // full screen. | ||||
|             var metadata = Object.create(FullscreenAction); | ||||
|             metadata.cssclass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse"; | ||||
|             metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse"; | ||||
|             metadata.description = screenfull.isFullscreen ? | ||||
|                 EXIT_FULLSCREEN : ENTER_FULLSCREEN; | ||||
|             metadata.group = "windowing"; | ||||
|   | ||||
| @@ -51,7 +51,7 @@ define( | ||||
|             }); | ||||
|  | ||||
|             it("provides displayable metadata", function () { | ||||
|                 expect(action.getMetadata().cssclass).toBeDefined(); | ||||
|                 expect(action.getMetadata().cssClass).toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|   | ||||
| @@ -163,7 +163,7 @@ define([ | ||||
|                     ], | ||||
|                     "description": "Edit", | ||||
|                     "category": "view-control", | ||||
|                     "cssclass": "major icon-pencil" | ||||
|                     "cssClass": "major icon-pencil" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "properties", | ||||
| @@ -172,7 +172,7 @@ define([ | ||||
|                         "view-control" | ||||
|                     ], | ||||
|                     "implementation": PropertiesAction, | ||||
|                     "cssclass": "major icon-pencil", | ||||
|                     "cssClass": "major icon-pencil", | ||||
|                     "name": "Edit Properties...", | ||||
|                     "description": "Edit properties of this object.", | ||||
|                     "depends": [ | ||||
| @@ -183,7 +183,7 @@ define([ | ||||
|                     "key": "remove", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": RemoveAction, | ||||
|                     "cssclass": "icon-trash", | ||||
|                     "cssClass": "icon-trash", | ||||
|                     "name": "Remove", | ||||
|                     "description": "Remove this object from its containing object.", | ||||
|                     "depends": [ | ||||
| @@ -195,7 +195,7 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAndStopEditingAction, | ||||
|                     "name": "Save and Finish Editing", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "dialogService", | ||||
| @@ -207,7 +207,7 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAction, | ||||
|                     "name": "Save and Continue Editing", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "dialogService", | ||||
| @@ -219,7 +219,7 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAsAction, | ||||
|                     "name": "Save As...", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "$injector", | ||||
| @@ -237,7 +237,7 @@ define([ | ||||
|                     // Because we use the name as label for edit buttons and mct-control buttons need | ||||
|                     // the label to be set to undefined in order to not apply the labeled CSS rule. | ||||
|                     "name": undefined, | ||||
|                     "cssclass": "icon-x no-label", | ||||
|                     "cssClass": "icon-x no-label", | ||||
|                     "description": "Discard changes made to these objects.", | ||||
|                     "depends": [] | ||||
|                 } | ||||
|   | ||||
| @@ -25,14 +25,14 @@ | ||||
|             <li ng-repeat="createAction in createActions" ng-click="createAction.perform()"> | ||||
|                 <a ng-mouseover="representation.activeMetadata = createAction.getMetadata()" | ||||
|                    ng-mouseleave="representation.activeMetadata = undefined" | ||||
|                    class="menu-item-a {{ createAction.getMetadata().cssclass }}"> | ||||
|                    class="menu-item-a {{ createAction.getMetadata().cssClass }}"> | ||||
|                     {{createAction.getMetadata().name}} | ||||
|                 </a> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class="pane right menu-item-description"> | ||||
|         <div class="desc-area icon {{ representation.activeMetadata.cssclass }}"></div> | ||||
|         <div class="desc-area icon {{ representation.activeMetadata.cssClass }}"></div> | ||||
|         <div class="desc-area title"> | ||||
|             {{representation.activeMetadata.name}} | ||||
|         </div> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|                      structure="{ | ||||
|                         text: saveActions[0].getMetadata().name, | ||||
|                         click: actionPerformer(saveActions[0]), | ||||
|                         cssclass: 'major ' + saveActions[0].getMetadata().cssclass | ||||
|                         cssClass: 'major ' + saveActions[0].getMetadata().cssClass | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
| @@ -36,7 +36,7 @@ | ||||
|                      structure="{ | ||||
|                         options: saveActionsAsMenuOptions, | ||||
|                         click: saveActionMenuClickHandler, | ||||
|                         cssclass: 'btn-bar right icon-save no-label major' | ||||
|                         cssClass: 'btn-bar right icon-save no-label major' | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
| @@ -46,7 +46,7 @@ | ||||
|                      structure="{ | ||||
|                         text: currentAction.getMetadata().name, | ||||
|                         click: actionPerformer(currentAction), | ||||
|                         cssclass: currentAction.getMetadata().cssclass | ||||
|                         cssClass: currentAction.getMetadata().cssClass | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
|   | ||||
| @@ -48,9 +48,10 @@ define( | ||||
|          * Decorate PersistenceCapability to queue persistence calls when a | ||||
|          * transaction is in progress. | ||||
|          */ | ||||
|         TransactionCapabilityDecorator.prototype.getCapabilities = function (model) { | ||||
|         TransactionCapabilityDecorator.prototype.getCapabilities = function () { | ||||
|             var self = this, | ||||
|                 capabilities = this.capabilityService.getCapabilities(model), | ||||
|                 capabilities = this.capabilityService.getCapabilities | ||||
|                     .apply(this.capabilityService, arguments), | ||||
|                 persistenceCapability = capabilities.persistence; | ||||
|  | ||||
|             capabilities.persistence = function (domainObject) { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define( | ||||
|                 return { | ||||
|                     key: action, | ||||
|                     name: action.getMetadata().name, | ||||
|                     cssclass: action.getMetadata().cssclass | ||||
|                     cssClass: action.getMetadata().cssClass | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ define( | ||||
|         function AddAction(type, parent, context, $q, dialogService, policyService) { | ||||
|             this.metadata = { | ||||
|                 key: 'add', | ||||
|                 cssclass: type.getCssClass(), | ||||
|                 cssClass: type.getCssClass(), | ||||
|                 name: type.getName(), | ||||
|                 type: type.getKey(), | ||||
|                 description: type.getDescription(), | ||||
|   | ||||
| @@ -66,9 +66,7 @@ define( | ||||
|             } | ||||
|  | ||||
|             // Introduce one create action per type | ||||
|             return this.typeService.listTypes().filter(function (type) { | ||||
|                 return self.policyService.allow("creation", type) && self.policyService.allow("composition", destination.getCapability('type'), type); | ||||
|             }).map(function (type) { | ||||
|             ['timeline', 'activity'].map(function (type) { | ||||
|                 return new AddAction( | ||||
|                     type, | ||||
|                     destination, | ||||
|   | ||||
| @@ -47,7 +47,7 @@ define( | ||||
|         function CreateAction(type, parent, context) { | ||||
|             this.metadata = { | ||||
|                 key: 'create', | ||||
|                 cssclass: type.getCssClass(), | ||||
|                 cssClass: type.getCssClass(), | ||||
|                 name: type.getName(), | ||||
|                 type: type.getKey(), | ||||
|                 description: type.getDescription(), | ||||
|   | ||||
| @@ -56,7 +56,7 @@ define( | ||||
|          */ | ||||
|         CreateWizard.prototype.getFormStructure = function (includeLocation) { | ||||
|             var sections = [], | ||||
|                 type = this.type, | ||||
|                 domainObject = this.domainObject, | ||||
|                 policyService = this.policyService; | ||||
|  | ||||
|             function validateLocation(locatingObject) { | ||||
| @@ -65,7 +65,7 @@ define( | ||||
|                 return locatingType && policyService.allow( | ||||
|                     "composition", | ||||
|                     locatingType, | ||||
|                     type | ||||
|                     domainObject | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
| @@ -91,7 +91,7 @@ define( | ||||
|             if (includeLocation) { | ||||
|                 sections.push({ | ||||
|                     name: 'Location', | ||||
|                     cssclass: "grows", | ||||
|                     cssClass: "grows", | ||||
|                     rows: [{ | ||||
|                         name: "Save In", | ||||
|                         control: "locator", | ||||
| @@ -118,7 +118,7 @@ define( | ||||
|                 formModel = this.createModel(formValue); | ||||
|  | ||||
|             formModel.location = parent.getId(); | ||||
|             this.domainObject.useCapability("mutation", function () { | ||||
|             this.domainObject.useCapability("mutation", function (model) { | ||||
|                 return formModel; | ||||
|             }); | ||||
|             return this.domainObject; | ||||
|   | ||||
| @@ -28,7 +28,7 @@ define( | ||||
|         describe("The Edit Action controller", function () { | ||||
|             var mockSaveActionMetadata = { | ||||
|                 name: "mocked-save-action", | ||||
|                 cssclass: "mocked-save-action-css" | ||||
|                 cssClass: "mocked-save-action-css" | ||||
|             }; | ||||
|  | ||||
|             function fakeGetActions(actionContext) { | ||||
| @@ -86,7 +86,7 @@ define( | ||||
|                 expect(menuOptions[1].key).toEqual(mockScope.saveActions[1]); | ||||
|                 menuOptions.forEach(function (option) { | ||||
|                     expect(option.name).toEqual(mockSaveActionMetadata.name); | ||||
|                     expect(option.cssclass).toEqual(mockSaveActionMetadata.cssclass); | ||||
|                     expect(option.cssClass).toEqual(mockSaveActionMetadata.cssClass); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -138,7 +138,7 @@ define( | ||||
|  | ||||
|                 expect(metadata.name).toEqual("Test"); | ||||
|                 expect(metadata.description).toEqual("a test type"); | ||||
|                 expect(metadata.cssclass).toEqual("icon-telemetry"); | ||||
|                 expect(metadata.cssClass).toEqual("icon-telemetry"); | ||||
|             }); | ||||
|  | ||||
|             describe("the perform function", function () { | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssclass}}" | ||||
| <a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssClass}}" | ||||
|    ng-class="{ labeled: parameters.labeled }" | ||||
|    title="{{parameters.action.getMetadata().description}}" | ||||
|    ng-click="parameters.action.perform()"> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="ViewSwitcherController"> | ||||
|     <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssclass}}" | ||||
|     <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssClass}}" | ||||
| 	    ng-if="view.length > 1" | ||||
| 	    ng-controller="ClickAwayController as toggle"> | ||||
|  | ||||
| @@ -33,7 +33,7 @@ | ||||
|             <ul> | ||||
|                 <li ng-repeat="option in view" | ||||
|                     ng-click="ngModel.selected = option; toggle.setState(false)" | ||||
|                     class="{{option.cssclass}}"> | ||||
|                     class="{{option.cssClass}}"> | ||||
|                     {{option.name}} | ||||
|                 </li> | ||||
|             </ul> | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|             <li ng-repeat="menuAction in menuActions" | ||||
|                 ng-click="menuAction.perform()" | ||||
|                 title="{{menuAction.getMetadata().description}}" | ||||
|                 class="{{menuAction.getMetadata().cssclass}}"> | ||||
|                 class="{{menuAction.getMetadata().cssClass}}"> | ||||
|                 {{menuAction.getMetadata().name}} | ||||
|             </li> | ||||
|         </ul> | ||||
|   | ||||
| @@ -40,9 +40,6 @@ define([ | ||||
|                 { | ||||
|                     "category": "composition", | ||||
|                     "implementation": CompositionPolicy, | ||||
|                     "depends": [ | ||||
|                         "$injector" | ||||
|                     ], | ||||
|                     "message": "Objects of this type cannot contain objects of that type." | ||||
|                 }, | ||||
|                 { | ||||
|   | ||||
| @@ -1,77 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Build a table indicating which types are expected to expose | ||||
|          * which capabilities. This supports composition policy (rules | ||||
|          * for which objects can contain which other objects) which | ||||
|          * sometimes is determined based on the presence of capabilities. | ||||
|          * @constructor | ||||
|          * @memberof platform/containment | ||||
|          */ | ||||
|         function CapabilityTable(typeService, capabilityService) { | ||||
|             var self = this; | ||||
|  | ||||
|             // Build an initial model for a type | ||||
|             function buildModel(type) { | ||||
|                 var model = Object.create(type.getInitialModel() || {}); | ||||
|                 model.type = type.getKey(); | ||||
|                 return model; | ||||
|             } | ||||
|  | ||||
|             // Get capabilities expected for this type | ||||
|             function getCapabilities(type) { | ||||
|                 return capabilityService.getCapabilities(buildModel(type)); | ||||
|             } | ||||
|  | ||||
|             // Populate the lookup table for this type's capabilities | ||||
|             function addToTable(type) { | ||||
|                 var typeKey = type.getKey(); | ||||
|                 Object.keys(getCapabilities(type)).forEach(function (key) { | ||||
|                     self.table[key] = self.table[key] || {}; | ||||
|                     self.table[key][typeKey] = true; | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             // Build the table | ||||
|             this.table = {}; | ||||
|             (typeService.listTypes() || []).forEach(addToTable); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Check if a type is expected to expose a specific capability. | ||||
|          * @param {string} typeKey the type identifier | ||||
|          * @param {string} capabilityKey the capability identifier | ||||
|          * @returns {boolean} true if expected to be exposed | ||||
|          */ | ||||
|         CapabilityTable.prototype.hasCapability = function (typeKey, capabilityKey) { | ||||
|             return (this.table[capabilityKey] || {})[typeKey]; | ||||
|         }; | ||||
|  | ||||
|         return CapabilityTable; | ||||
|     } | ||||
| ); | ||||
| @@ -45,9 +45,7 @@ define( | ||||
|         ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) { | ||||
|             // Get the object types involved in the compose action | ||||
|             var containerType = containerObject && | ||||
|                     containerObject.getCapability('type'), | ||||
|                 selectedType = selectedObject && | ||||
|                     selectedObject.getCapability('type'); | ||||
|                 containerObject.getCapability('type'); | ||||
|  | ||||
|             // Get a reference to the policy service if needed... | ||||
|             this.policyService = this.policyService || this.getPolicyService(); | ||||
| @@ -57,7 +55,7 @@ define( | ||||
|                 this.policyService.allow( | ||||
|                     'composition', | ||||
|                     containerType, | ||||
|                     selectedType | ||||
|                     selectedObject | ||||
|                 ); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,8 @@ | ||||
|  * @namespace platform/containment | ||||
|  */ | ||||
| define( | ||||
|     ['./ContainmentTable'], | ||||
|     function (ContainmentTable) { | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Defines composition policy as driven by type metadata. | ||||
| @@ -35,21 +35,35 @@ define( | ||||
|          * @memberof platform/containment | ||||
|          * @implements {Policy.<Type, Type>} | ||||
|          */ | ||||
|         function CompositionPolicy($injector) { | ||||
|             // We're really just wrapping the containment table and rephrasing | ||||
|             // it as a policy decision. | ||||
|             var table; | ||||
|  | ||||
|             this.getTable = function () { | ||||
|                 return (table = table || new ContainmentTable( | ||||
|                     $injector.get('typeService'), | ||||
|                     $injector.get('capabilityService') | ||||
|                 )); | ||||
|             }; | ||||
|         function CompositionPolicy() { | ||||
|         } | ||||
|  | ||||
|         CompositionPolicy.prototype.allow = function (candidate, context) { | ||||
|             return this.getTable().canContain(candidate, context); | ||||
|             var type = context.getCapability('type'); | ||||
|             var typeKey = type.getKey(); | ||||
|             var candidateDef = candidate.getDefinition(); | ||||
|              | ||||
|             // A candidate without containment rules can contain anything. | ||||
|             if (!candidateDef.contains) { | ||||
|                 return true; | ||||
|             } | ||||
|              | ||||
|             // If any containment rule matches context type, the candidate | ||||
|             // can contain this type. | ||||
|             return candidateDef.contains.some(function (c) { | ||||
|                 // Simple containment rules are supported typeKeys. | ||||
|                 if (typeof c === 'string') { | ||||
|                     return c === typeKey; | ||||
|                 } | ||||
|                 // More complicated rules require context to have all specified | ||||
|                 // capabilities. | ||||
|                 if (!Array.isArray(c.has)) { | ||||
|                     c.has = [c.has]; | ||||
|                 } | ||||
|                 return c.has.every(function (capability) { | ||||
|                     return context.hasCapability(capability); | ||||
|                 }); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         return CompositionPolicy; | ||||
|   | ||||
| @@ -1,116 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ['./CapabilityTable'], | ||||
|     function (CapabilityTable) { | ||||
|  | ||||
|         // Symbolic value for the type table for cases when any type | ||||
|         // is allowed to be contained. | ||||
|         var ANY = true; | ||||
|  | ||||
|         /** | ||||
|          * Supports composition policy by maintaining a table of | ||||
|          * domain object types, to determine if they can contain | ||||
|          * other domain object types. This is determined at application | ||||
|          * start time (plug-in support means this cannot be determined | ||||
|          * prior to that, but we don't want to redo these calculations | ||||
|          * every time policy is checked.) | ||||
|          * @constructor | ||||
|          * @memberof platform/containment | ||||
|          */ | ||||
|         function ContainmentTable(typeService, capabilityService) { | ||||
|             var self = this, | ||||
|                 types = typeService.listTypes(), | ||||
|                 capabilityTable = new CapabilityTable(typeService, capabilityService); | ||||
|  | ||||
|             // Add types which have all these capabilities to the set | ||||
|             // of allowed types | ||||
|             function addToSetByCapability(set, has) { | ||||
|                 has = Array.isArray(has) ? has : [has]; | ||||
|                 types.forEach(function (type) { | ||||
|                     var typeKey = type.getKey(); | ||||
|                     set[typeKey] = has.map(function (capabilityKey) { | ||||
|                         return capabilityTable.hasCapability(typeKey, capabilityKey); | ||||
|                     }).reduce(function (a, b) { | ||||
|                         return a && b; | ||||
|                     }, true); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             // Add this type (or type description) to the set of allowed types | ||||
|             function addToSet(set, type) { | ||||
|                 // Is this a simple case of an explicit type identifier? | ||||
|                 if (typeof type === 'string') { | ||||
|                     // If so, add it to the set of allowed types | ||||
|                     set[type] = true; | ||||
|                 } else { | ||||
|                     // Otherwise, populate that set based on capabilities | ||||
|                     addToSetByCapability(set, (type || {}).has || []); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Add to the lookup table for this type | ||||
|             function addToTable(type) { | ||||
|                 var key = type.getKey(), | ||||
|                     definition = type.getDefinition() || {}, | ||||
|                     contains = definition.contains; | ||||
|  | ||||
|                 // Check for defined containment restrictions | ||||
|                 if (contains === undefined) { | ||||
|                     // If not, accept anything | ||||
|                     self.table[key] = ANY; | ||||
|                 } else { | ||||
|                     // Start with an empty set... | ||||
|                     self.table[key] = {}; | ||||
|                     // ...cast accepted types to array if necessary... | ||||
|                     contains = Array.isArray(contains) ? contains : [contains]; | ||||
|                     // ...and add all containment rules to that set | ||||
|                     contains.forEach(function (c) { | ||||
|                         addToSet(self.table[key], c); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Build the table | ||||
|             this.table = {}; | ||||
|             types.forEach(addToTable); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Check if domain objects of one type can contain domain | ||||
|          * objects of another type. | ||||
|          * @param {Type} containerType type of the containing domain object | ||||
|          * @param {Type} containedType type of the domain object | ||||
|          *        to be contained | ||||
|          * @returns {boolean} true if allowable | ||||
|          */ | ||||
|         ContainmentTable.prototype.canContain = function (containerType, containedType) { | ||||
|             var set = this.table[containerType.getKey()] || {}; | ||||
|             // Recognize either the symbolic value for "can contain | ||||
|             // anything", or lookup the specific type from the set. | ||||
|             return (set === ANY) || set[containedType.getKey()]; | ||||
|         }; | ||||
|  | ||||
|         return ContainmentTable; | ||||
|     } | ||||
| ); | ||||
| @@ -1,85 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../src/CapabilityTable"], | ||||
|     function (CapabilityTable) { | ||||
|         describe("Composition policy's capability table", function () { | ||||
|             var mockTypeService, | ||||
|                 mockCapabilityService, | ||||
|                 mockTypes, | ||||
|                 table; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockTypeService = jasmine.createSpyObj( | ||||
|                     'typeService', | ||||
|                     ['listTypes'] | ||||
|                 ); | ||||
|                 mockCapabilityService = jasmine.createSpyObj( | ||||
|                     'capabilityService', | ||||
|                     ['getCapabilities'] | ||||
|                 ); | ||||
|                 // Both types can only contain b, let's say | ||||
|                 mockTypes = ['a', 'b'].map(function (type) { | ||||
|                     var mockType = jasmine.createSpyObj( | ||||
|                         'type-' + type, | ||||
|                         ['getKey', 'getDefinition', 'getInitialModel'] | ||||
|                     ); | ||||
|                     mockType.getKey.andReturn(type); | ||||
|                     // Return a model to drive apparent capabilities | ||||
|                     mockType.getInitialModel.andReturn({ id: type }); | ||||
|                     return mockType; | ||||
|                 }); | ||||
|  | ||||
|                 mockTypeService.listTypes.andReturn(mockTypes); | ||||
|                 mockCapabilityService.getCapabilities.andCallFake(function (model) { | ||||
|                     var capabilities = {}; | ||||
|                     capabilities[model.id + '-capability'] = true; | ||||
|                     return capabilities; | ||||
|                 }); | ||||
|  | ||||
|                 table = new CapabilityTable( | ||||
|                     mockTypeService, | ||||
|                     mockCapabilityService | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("provides for lookup of capabilities by type", function () { | ||||
|                 // Based on initial model, should report the presence | ||||
|                 // of particular capabilities - suffixed above with -capability | ||||
|                 expect(table.hasCapability('a', 'a-capability')) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.hasCapability('a', 'b-capability')) | ||||
|                     .toBeFalsy(); | ||||
|                 expect(table.hasCapability('a', 'c-capability')) | ||||
|                     .toBeFalsy(); | ||||
|                 expect(table.hasCapability('b', 'a-capability')) | ||||
|                     .toBeFalsy(); | ||||
|                 expect(table.hasCapability('b', 'b-capability')) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.hasCapability('b', 'c-capability')) | ||||
|                     .toBeFalsy(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,96 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../src/ContainmentTable"], | ||||
|     function (ContainmentTable) { | ||||
|         describe("Composition policy's containment table", function () { | ||||
|             var mockTypeService, | ||||
|                 mockCapabilityService, | ||||
|                 mockTypes, | ||||
|                 table; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockTypeService = jasmine.createSpyObj( | ||||
|                     'typeService', | ||||
|                     ['listTypes'] | ||||
|                 ); | ||||
|                 mockCapabilityService = jasmine.createSpyObj( | ||||
|                     'capabilityService', | ||||
|                     ['getCapabilities'] | ||||
|                 ); | ||||
|                 // Both types can only contain b, let's say | ||||
|                 mockTypes = ['a', 'b', 'c'].map(function (type, index) { | ||||
|                     var mockType = jasmine.createSpyObj( | ||||
|                         'type-' + type, | ||||
|                         ['getKey', 'getDefinition', 'getInitialModel'] | ||||
|                     ); | ||||
|                     mockType.getKey.andReturn(type); | ||||
|                     mockType.getDefinition.andReturn({ | ||||
|                         // First two contain objects with capability 'b'; | ||||
|                         // third one defines no containership rules | ||||
|                         contains: (index < 2) ? [{ has: 'b' }] : undefined | ||||
|                     }); | ||||
|                     // Return a model to drive apparent capabilities | ||||
|                     mockType.getInitialModel.andReturn({ id: type }); | ||||
|                     return mockType; | ||||
|                 }); | ||||
|  | ||||
|                 mockTypeService.listTypes.andReturn(mockTypes); | ||||
|                 mockCapabilityService.getCapabilities.andCallFake(function (model) { | ||||
|                     var capabilities = {}; | ||||
|                     capabilities[model.id] = true; | ||||
|                     return capabilities; | ||||
|                 }); | ||||
|  | ||||
|                 table = new ContainmentTable( | ||||
|                     mockTypeService, | ||||
|                     mockCapabilityService | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             // The plain type case is tested in CompositionPolicySpec, | ||||
|             // so just test for special syntax ('has', or no contains rules) here | ||||
|             it("enforces 'has' containment rules related to capabilities", function () { | ||||
|                 expect(table.canContain(mockTypes[0], mockTypes[1])) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.canContain(mockTypes[1], mockTypes[1])) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.canContain(mockTypes[1], mockTypes[0])) | ||||
|                     .toBeFalsy(); | ||||
|                 expect(table.canContain(mockTypes[0], mockTypes[0])) | ||||
|                     .toBeFalsy(); | ||||
|             }); | ||||
|  | ||||
|             it("allows anything when no containership rules are defined", function () { | ||||
|                 expect(table.canContain(mockTypes[2], mockTypes[0])) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.canContain(mockTypes[2], mockTypes[1])) | ||||
|                     .toBeTruthy(); | ||||
|                 expect(table.canContain(mockTypes[2], mockTypes[2])) | ||||
|                     .toBeTruthy(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -241,7 +241,7 @@ define([ | ||||
|                             "property": "name", | ||||
|                             "pattern": "\\S+", | ||||
|                             "required": true, | ||||
|                             "cssclass": "l-input-lg" | ||||
|                             "cssClass": "l-input-lg" | ||||
|                         }, | ||||
|                         { | ||||
|                             "name": "Notes", | ||||
| @@ -249,19 +249,19 @@ define([ | ||||
|                             "property": "notes", | ||||
|                             "control": "textarea", | ||||
|                             "required": false, | ||||
|                             "cssclass": "l-textarea-sm" | ||||
|                             "cssClass": "l-textarea-sm" | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "root", | ||||
|                     "name": "Root", | ||||
|                     "cssclass": "icon-folder" | ||||
|                     "cssClass": "icon-folder" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "folder", | ||||
|                     "name": "Folder", | ||||
|                     "cssclass": "icon-folder", | ||||
|                     "cssClass": "icon-folder", | ||||
|                     "features": "creation", | ||||
|                     "description": "Create folders to organize other objects or links to objects.", | ||||
|                     "priority": 1000, | ||||
| @@ -272,11 +272,11 @@ define([ | ||||
|                 { | ||||
|                     "key": "unknown", | ||||
|                     "name": "Unknown Type", | ||||
|                     "cssclass": "icon-object-unknown" | ||||
|                     "cssClass": "icon-object-unknown" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Unknown Type", | ||||
|                     "cssclass": "icon-object-unknown" | ||||
|                     "cssClass": "icon-object-unknown" | ||||
|                 } | ||||
|             ], | ||||
|             "capabilities": [ | ||||
|   | ||||
| @@ -58,7 +58,7 @@ define( | ||||
|          * @property {string} key machine-readable identifier for this action | ||||
|          * @property {string} name human-readable name for this action | ||||
|          * @property {string} description human-readable description | ||||
|          * @property {string} cssclass CSS class for icon | ||||
|          * @property {string} cssClass CSS class for icon | ||||
|          * @property {ActionContext} context the context in which the action | ||||
|          *           will be performed. | ||||
|          */ | ||||
|   | ||||
| @@ -53,10 +53,10 @@ define( | ||||
|          */ | ||||
|         function CoreCapabilityProvider(capabilities, $log) { | ||||
|             // Filter by invoking the capability's appliesTo method | ||||
|             function filterCapabilities(model) { | ||||
|             function filterCapabilities(model, id) { | ||||
|                 return capabilities.filter(function (capability) { | ||||
|                     return capability.appliesTo ? | ||||
|                             capability.appliesTo(model) : | ||||
|                             capability.appliesTo(model, id) : | ||||
|                             true; | ||||
|                 }); | ||||
|             } | ||||
| @@ -75,8 +75,8 @@ define( | ||||
|                 return result; | ||||
|             } | ||||
|  | ||||
|             function getCapabilities(model) { | ||||
|                 return packageCapabilities(filterCapabilities(model)); | ||||
|             function getCapabilities(model, id) { | ||||
|                 return packageCapabilities(filterCapabilities(model, id)); | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|   | ||||
| @@ -44,16 +44,16 @@ define( | ||||
|                 var model = parentObject && parentObject.getModel(), | ||||
|                     composition = (model || {}).composition || []; | ||||
|                 if (composition.indexOf(id) === -1) { | ||||
|                     $log.warn([ | ||||
|                         "Attempted to contextualize", | ||||
|                         id, | ||||
|                         "in", | ||||
|                         parentObject && parentObject.getId(), | ||||
|                         "but that object does not contain", | ||||
|                         id, | ||||
|                         "in its composition.", | ||||
|                         "Unexpected behavior may follow." | ||||
|                     ].join(" ")); | ||||
|                     // $log.warn([ | ||||
|                     //     "Attempted to contextualize", | ||||
|                     //     id, | ||||
|                     //     "in", | ||||
|                     //     parentObject && parentObject.getId(), | ||||
|                     //     "but that object does not contain", | ||||
|                     //     id, | ||||
|                     //     "in its composition.", | ||||
|                     //     "Unexpected behavior may follow." | ||||
|                     // ].join(" ")); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -56,12 +56,12 @@ define( | ||||
|          * @method Type#getDescription | ||||
|          */ | ||||
|         /** | ||||
|          * Get the cssclass associated with this type. cssclass is a | ||||
|          * Get the cssClass associated with this type. cssClass is a | ||||
|          * string which will appear as an icon (when | ||||
|          * displayed in an appropriate font) which visually | ||||
|          * distinguish types from one another. | ||||
|          * | ||||
|          * @returns {string} the cssclass for this type | ||||
|          * @returns {string} the cssClass for this type | ||||
|          * @method Type#getCssClass | ||||
|          */ | ||||
|         /** | ||||
| @@ -145,7 +145,7 @@ define( | ||||
|         }; | ||||
|  | ||||
|         TypeImpl.prototype.getCssClass = function () { | ||||
|             return this.typeDef.cssclass; | ||||
|             return this.typeDef.cssClass; | ||||
|         }; | ||||
|  | ||||
|         TypeImpl.prototype.getProperties = function () { | ||||
|   | ||||
| @@ -33,7 +33,7 @@ define( | ||||
|                     key: 'test-type', | ||||
|                     name: 'Test Type', | ||||
|                     description: 'A type, for testing', | ||||
|                     cssclass: 'icon-telemetry-panel', | ||||
|                     cssClass: 'icon-telemetry-panel', | ||||
|                     inherits: ['test-parent-1', 'test-parent-2'], | ||||
|                     features: ['test-feature-1'], | ||||
|                     properties: [{}], | ||||
|   | ||||
| @@ -30,18 +30,18 @@ define( | ||||
|                 testTypeDefinitions = [ | ||||
|                     { | ||||
|                         key: 'basic', | ||||
|                         cssclass: "icon-magnify-in", | ||||
|                         cssClass: "icon-magnify-in", | ||||
|                         name: "Basic Type" | ||||
|                     }, | ||||
|                     { | ||||
|                         key: 'multi1', | ||||
|                         cssclass: "icon-trash", | ||||
|                         cssClass: "icon-trash", | ||||
|                         description: "Multi1 Description", | ||||
|                         capabilities: ['a1', 'b1'] | ||||
|                     }, | ||||
|                     { | ||||
|                         key: 'multi2', | ||||
|                         cssclass: "icon-magnify-out", | ||||
|                         cssClass: "icon-magnify-out", | ||||
|                         capabilities: ['a2', 'b2', 'c2'] | ||||
|                     }, | ||||
|                     { | ||||
|   | ||||
| @@ -66,7 +66,7 @@ define([ | ||||
|                     "key": "move", | ||||
|                     "name": "Move", | ||||
|                     "description": "Move object to another location.", | ||||
|                     "cssclass": "icon-move", | ||||
|                     "cssClass": "icon-move", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": MoveAction, | ||||
|                     "depends": [ | ||||
| @@ -79,7 +79,7 @@ define([ | ||||
|                     "key": "copy", | ||||
|                     "name": "Duplicate", | ||||
|                     "description": "Duplicate object to another location.", | ||||
|                     "cssclass": "icon-duplicate", | ||||
|                     "cssClass": "icon-duplicate", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": CopyAction, | ||||
|                     "depends": [ | ||||
| @@ -95,7 +95,7 @@ define([ | ||||
|                     "key": "link", | ||||
|                     "name": "Create Link", | ||||
|                     "description": "Create Link to object in another location.", | ||||
|                     "cssclass": "icon-link", | ||||
|                     "cssClass": "icon-link", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": LinkAction, | ||||
|                     "depends": [ | ||||
| @@ -108,7 +108,7 @@ define([ | ||||
|                     "key": "follow", | ||||
|                     "name": "Go To Original", | ||||
|                     "description": "Go to the original, un-linked instance of this object.", | ||||
|                     "cssclass": "", | ||||
|                     "cssClass": "", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": GoToOriginalAction | ||||
|                 }, | ||||
| @@ -116,7 +116,7 @@ define([ | ||||
|                     "key": "locate", | ||||
|                     "name": "Set Primary Location", | ||||
|                     "description": "Set a domain object's primary location.", | ||||
|                     "cssclass": "", | ||||
|                     "cssClass": "", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": SetPrimaryLocationAction | ||||
|                 } | ||||
|   | ||||
| @@ -48,7 +48,7 @@ define( | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate.getCapability('type'), | ||||
|                 object.getCapability('type') | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,7 @@ define( | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate.getCapability('type'), | ||||
|                 object.getCapability('type') | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -58,7 +58,7 @@ define( | ||||
|                         sections: [ | ||||
|                             { | ||||
|                                 name: 'Location', | ||||
|                                 cssclass: "grows", | ||||
|                                 cssClass: "grows", | ||||
|                                 rows: [ | ||||
|                                     { | ||||
|                                         name: label, | ||||
|   | ||||
| @@ -56,7 +56,7 @@ define( | ||||
|             return this.policyService.allow( | ||||
|                 "composition", | ||||
|                 parentCandidate.getCapability('type'), | ||||
|                 object.getCapability('type') | ||||
|                 object | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -136,7 +136,7 @@ define([ | ||||
|                     ], | ||||
|                     "category": "contextual", | ||||
|                     "name": "Start", | ||||
|                     "cssclass": "icon-play", | ||||
|                     "cssClass": "icon-play", | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 { | ||||
| @@ -147,7 +147,7 @@ define([ | ||||
|                     ], | ||||
|                     "category": "contextual", | ||||
|                     "name": "Restart at 0", | ||||
|                     "cssclass": "icon-refresh", | ||||
|                     "cssClass": "icon-refresh", | ||||
|                     "priority": "preferred" | ||||
|                 } | ||||
|             ], | ||||
| @@ -155,7 +155,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "clock", | ||||
|                     "name": "Clock", | ||||
|                     "cssclass": "icon-clock", | ||||
|                     "cssClass": "icon-clock", | ||||
|                     "description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.", | ||||
|                     "priority": 101, | ||||
|                     "features": [ | ||||
| @@ -183,7 +183,7 @@ define([ | ||||
|                                             "name": "hh:mm:ss" | ||||
|                                         } | ||||
|                                     ], | ||||
|                                     "cssclass": "l-inline" | ||||
|                                     "cssClass": "l-inline" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "control": "select", | ||||
| @@ -197,7 +197,7 @@ define([ | ||||
|                                             "name": "24hr" | ||||
|                                         } | ||||
|                                     ], | ||||
|                                     "cssclass": "l-inline" | ||||
|                                     "cssClass": "l-inline" | ||||
|                                 } | ||||
|                             ] | ||||
|                         } | ||||
| @@ -212,7 +212,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "timer", | ||||
|                     "name": "Timer", | ||||
|                     "cssclass": "icon-timer", | ||||
|                     "cssClass": "icon-timer", | ||||
|                     "description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.", | ||||
|                     "priority": 100, | ||||
|                     "features": [ | ||||
|   | ||||
| @@ -131,11 +131,11 @@ define( | ||||
|         /** | ||||
|          * Get the CSS class to display the right icon | ||||
|          * for the start/restart button. | ||||
|          * @returns {string} cssclass to display | ||||
|          * @returns {string} cssClass to display | ||||
|          */ | ||||
|         TimerController.prototype.buttonCssClass = function () { | ||||
|             return this.relevantAction ? | ||||
|                     this.relevantAction.getMetadata().cssclass : ""; | ||||
|                     this.relevantAction.getMetadata().cssClass : ""; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -85,8 +85,8 @@ define( | ||||
|                         'timer.restart': mockRestart | ||||
|                     }[k]]; | ||||
|                 }); | ||||
|                 mockStart.getMetadata.andReturn({ cssclass: "icon-play", name: "Start" }); | ||||
|                 mockRestart.getMetadata.andReturn({ cssclass: "icon-refresh", name: "Restart" }); | ||||
|                 mockStart.getMetadata.andReturn({ cssClass: "icon-play", name: "Start" }); | ||||
|                 mockRestart.getMetadata.andReturn({ cssClass: "icon-refresh", name: "Restart" }); | ||||
|                 mockScope.domainObject = mockDomainObject; | ||||
|  | ||||
|                 testModel = {}; | ||||
| @@ -144,7 +144,7 @@ define( | ||||
|                 expect(controller.text()).toEqual("0D 00:00:00"); | ||||
|             }); | ||||
|  | ||||
|             it("shows cssclass & name for the applicable start/restart action", function () { | ||||
|             it("shows cssClass & name for the applicable start/restart action", function () { | ||||
|                 invokeWatch('domainObject', mockDomainObject); | ||||
|                 expect(controller.buttonCssClass()).toEqual("icon-play"); | ||||
|                 expect(controller.buttonText()).toEqual("Start"); | ||||
|   | ||||
| @@ -70,8 +70,9 @@ define([ | ||||
|                         "$location", | ||||
|                         "openmct", | ||||
|                         "timeConductorViewService", | ||||
|                         "timeSystems[]", | ||||
|                         "formatService" | ||||
|                         "formatService", | ||||
|                         "DEFAULT_TIMECONDUCTOR_MODE", | ||||
|                         "SHOW_TIMECONDUCTOR", | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| @@ -150,6 +151,13 @@ define([ | ||||
|                     "link": "https://github.com/d3/d3/blob/master/LICENSE" | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "DEFAULT_TIMECONDUCTOR_MODE", | ||||
|                     "value": "realtime", | ||||
|                     "priority": "fallback" | ||||
|                 } | ||||
|             ], | ||||
|             "formats": [ | ||||
|                 { | ||||
|                     "key": "number", | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|                 ng-click="ngModel.selectedKey=key"> | ||||
|                 <a ng-mouseover="ngModel.activeMetadata = metadata" | ||||
|                    ng-mouseleave="ngModel.activeMetadata = undefined" | ||||
|                    class="menu-item-a {{metadata.cssclass}}"> | ||||
|                    class="menu-item-a {{metadata.cssClass}}"> | ||||
|                     {{metadata.name}} | ||||
|                 </a> | ||||
|             </li> | ||||
| @@ -34,7 +34,7 @@ | ||||
|     </div> | ||||
|     <div class="pane right menu-item-description"> | ||||
|         <div | ||||
|                 class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssclass}}"></div> | ||||
|                 class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div> | ||||
|         <div class="desc-area title"> | ||||
|             {{ngModel.activeMetadata.name}} | ||||
|         </div> | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| <!-- Parent holder for time conductor. follow-mode | fixed-mode --> | ||||
| <div ng-controller="TimeConductorController as tcController" | ||||
|     class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system" | ||||
|         ng-class="{'status-panning': tcController.panning}"> | ||||
|  | ||||
|         ng-class="{'status-panning': tcController.panning}" ng-show="showTimeConductor"> | ||||
|     <div class="flex-elem holder time-conductor-icon"> | ||||
|         <div class="hand-little"></div> | ||||
|         <div class="hand-big"></div> | ||||
|   | ||||
| @@ -31,7 +31,7 @@ define(['./TickSource'], function (TickSource) { | ||||
|         this.metadata = { | ||||
|             key: 'local', | ||||
|             mode: 'realtime', | ||||
|             cssclass: 'icon-clock', | ||||
|             cssClass: 'icon-clock', | ||||
|             label: 'Real-time', | ||||
|             name: 'Real-time Mode', | ||||
|             description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.' | ||||
|   | ||||
| @@ -40,7 +40,16 @@ define( | ||||
|          * @memberof platform.features.conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorController($scope, $window, $location, openmct, conductorViewService, timeSystems, formatService) { | ||||
|         function TimeConductorController( | ||||
|             $scope,  | ||||
|             $window,  | ||||
|             $location,  | ||||
|             openmct,  | ||||
|             conductorViewService, | ||||
|             formatService, | ||||
|             DEFAULT_MODE, | ||||
|             SHOW_TIMECONDUCTOR | ||||
|         ) { | ||||
|  | ||||
|             var self = this; | ||||
|  | ||||
| @@ -59,11 +68,10 @@ define( | ||||
|             this.modes = conductorViewService.availableModes(); | ||||
|             this.validation = new TimeConductorValidation(this.conductor); | ||||
|             this.formatService = formatService; | ||||
|             this.DEFAULT_MODE = DEFAULT_MODE; | ||||
|  | ||||
|             // Construct the provided time system definitions | ||||
|             this.timeSystems = timeSystems.map(function (timeSystemConstructor) { | ||||
|                 return timeSystemConstructor(); | ||||
|             }); | ||||
|             this.timeSystems = conductorViewService.systems; | ||||
|  | ||||
|             this.initializeScope(); | ||||
|             var searchParams = JSON.parse(JSON.stringify(this.$location.search())); | ||||
| @@ -94,6 +102,8 @@ define( | ||||
|             //Respond to any subsequent conductor changes | ||||
|             this.conductor.on('bounds', this.changeBounds); | ||||
|             this.conductor.on('timeSystem', this.changeTimeSystem); | ||||
|  | ||||
|             this.$scope.showTimeConductor = SHOW_TIMECONDUCTOR; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -139,7 +149,7 @@ define( | ||||
|             //Set mode from url if changed | ||||
|             if (searchParams[SEARCH.MODE] === undefined || | ||||
|                 searchParams[SEARCH.MODE] !== this.$scope.modeModel.selectedKey) { | ||||
|                 this.setMode(searchParams[SEARCH.MODE] || "fixed"); | ||||
|                 this.setMode(searchParams[SEARCH.MODE] || this.DEFAULT_MODE); | ||||
|             } | ||||
|  | ||||
|             if (searchParams[SEARCH.TIME_SYSTEM] && | ||||
|   | ||||
| @@ -60,7 +60,7 @@ define( | ||||
|             this.availModes = { | ||||
|                 'fixed': { | ||||
|                     key: 'fixed', | ||||
|                     cssclass: 'icon-calendar', | ||||
|                     cssClass: 'icon-calendar', | ||||
|                     label: 'Fixed', | ||||
|                     name: 'Fixed Timespan Mode', | ||||
|                     description: 'Query and explore data that falls between two fixed datetimes.' | ||||
| @@ -81,7 +81,7 @@ define( | ||||
|             if (timeSystemsForMode('realtime').length > 0) { | ||||
|                 var realtimeMode = { | ||||
|                     key: 'realtime', | ||||
|                     cssclass: 'icon-clock', | ||||
|                     cssClass: 'icon-clock', | ||||
|                     label: 'Real-time', | ||||
|                     name: 'Real-time Mode', | ||||
|                     description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.' | ||||
| @@ -93,7 +93,7 @@ define( | ||||
|             if (timeSystemsForMode('lad').length > 0) { | ||||
|                 var ladMode = { | ||||
|                     key: 'lad', | ||||
|                     cssclass: 'icon-database', | ||||
|                     cssClass: 'icon-database', | ||||
|                     label: 'LAD', | ||||
|                     name: 'LAD Mode', | ||||
|                     description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.' | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| define([ | ||||
|     "./src/UTCTimeSystem", | ||||
|     'legacyRegistry' | ||||
|     "legacyRegistry" | ||||
| ], function ( | ||||
|     UTCTimeSystem, | ||||
|     legacyRegistry | ||||
|   | ||||
| @@ -25,7 +25,7 @@ define([ | ||||
|     '../../core/src/timeSystems/LocalClock' | ||||
| ], function (TimeSystem, LocalClock) { | ||||
|     var FIFTEEN_MINUTES = 15 * 60 * 1000, | ||||
|         DEFAULT_PERIOD = 1000; | ||||
|         DEFAULT_PERIOD = 100; | ||||
|  | ||||
|     /** | ||||
|      * This time system supports UTC dates and provides a ticking clock source. | ||||
| @@ -38,16 +38,17 @@ define([ | ||||
|         /** | ||||
|          * Some metadata, which will be used to identify the time system in | ||||
|          * the UI | ||||
|          * @type {{key: string, name: string, cssclass: string}} | ||||
|          * @type {{key: string, name: string, cssClass: string}} | ||||
|          */ | ||||
|         this.metadata = { | ||||
|             'key': 'utc', | ||||
|             'name': 'UTC', | ||||
|             'cssclass': 'icon-clock' | ||||
|             'cssClass': 'icon-clock' | ||||
|         }; | ||||
|  | ||||
|         this.fmts = ['utc']; | ||||
|         this.sources = [new LocalClock($timeout, DEFAULT_PERIOD)]; | ||||
|         this.defaultValues = undefined; | ||||
|     } | ||||
|  | ||||
|     UTCTimeSystem.prototype = Object.create(TimeSystem.prototype); | ||||
| @@ -64,18 +65,25 @@ define([ | ||||
|         return this.sources; | ||||
|     }; | ||||
|  | ||||
|     UTCTimeSystem.prototype.defaults = function () { | ||||
|         var now = Math.ceil(Date.now() / 1000) * 1000; | ||||
|         var ONE_MINUTE = 60 * 1 * 1000; | ||||
|         var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000; | ||||
|     UTCTimeSystem.prototype.defaults = function (defaults) { | ||||
|         if (arguments.length > 0){ | ||||
|             this.defaultValues = defaults; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             key: 'utc-default', | ||||
|             name: 'UTC time system defaults', | ||||
|             deltas: {start: FIFTEEN_MINUTES, end: 0}, | ||||
|             bounds: {start: now - FIFTEEN_MINUTES, end: now}, | ||||
|             zoom: {min: FIFTY_YEARS, max: ONE_MINUTE} | ||||
|         }; | ||||
|         if (this.defaultValues === undefined) { | ||||
|             var now = Math.ceil(Date.now() / 1000) * 1000; | ||||
|             var ONE_MINUTE = 60 * 1 * 1000; | ||||
|             var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000; | ||||
|  | ||||
|             this.defaultValues = { | ||||
|                 key: 'utc-default', | ||||
|                 name: 'UTC time system defaults', | ||||
|                 deltas: {start: FIFTEEN_MINUTES, end: 0}, | ||||
|                 bounds: {start: now - FIFTEEN_MINUTES, end: now}, | ||||
|                 zoom: {min: FIFTY_YEARS, max: ONE_MINUTE} | ||||
|             } | ||||
|         } | ||||
|         return this.defaultValues; | ||||
|     }; | ||||
|  | ||||
|     return UTCTimeSystem; | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "fixed-display", | ||||
|                     "name": "Fixed Position Display", | ||||
|                     "cssclass": "icon-box-with-dashed-lines", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "type": "telemetry.fixed", | ||||
|                     "template": fixedTemplate, | ||||
|                     "uses": [ | ||||
| @@ -49,28 +49,28 @@ define([ | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "add", | ||||
|                                         "cssclass": "icon-plus", | ||||
|                                         "cssClass": "icon-plus", | ||||
|                                         "control": "menu-button", | ||||
|                                         "text": "Add", | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Box", | ||||
|                                                 "cssclass": "icon-box", | ||||
|                                                 "cssClass": "icon-box", | ||||
|                                                 "key": "fixed.box" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Line", | ||||
|                                                 "cssclass": "icon-line-horz", | ||||
|                                                 "cssClass": "icon-line-horz", | ||||
|                                                 "key": "fixed.line" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Text", | ||||
|                                                 "cssclass": "icon-T", | ||||
|                                                 "cssClass": "icon-T", | ||||
|                                                 "key": "fixed.text" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Image", | ||||
|                                                 "cssclass": "icon-image", | ||||
|                                                 "cssClass": "icon-image", | ||||
|                                                 "key": "fixed.image" | ||||
|                                             } | ||||
|                                         ] | ||||
| @@ -81,50 +81,50 @@ define([ | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "order", | ||||
|                                         "cssclass": "icon-layers", | ||||
|                                         "cssClass": "icon-layers", | ||||
|                                         "control": "menu-button", | ||||
|                                         "title": "Layering", | ||||
|                                         "description": "Move the selected object above or below other objects", | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Move to Top", | ||||
|                                                 "cssclass": "icon-arrow-double-up", | ||||
|                                                 "cssClass": "icon-arrow-double-up", | ||||
|                                                 "key": "top" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Up", | ||||
|                                                 "cssclass": "icon-arrow-up", | ||||
|                                                 "cssClass": "icon-arrow-up", | ||||
|                                                 "key": "up" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Down", | ||||
|                                                 "cssclass": "icon-arrow-down", | ||||
|                                                 "cssClass": "icon-arrow-down", | ||||
|                                                 "key": "down" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move to Bottom", | ||||
|                                                 "cssclass": "icon-arrow-double-down", | ||||
|                                                 "cssClass": "icon-arrow-double-down", | ||||
|                                                 "key": "bottom" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "fill", | ||||
|                                         "cssclass": "icon-paint-bucket", | ||||
|                                         "cssClass": "icon-paint-bucket", | ||||
|                                         "title": "Fill color", | ||||
|                                         "description": "Set fill color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "stroke", | ||||
|                                         "cssclass": "icon-line-horz", | ||||
|                                         "cssClass": "icon-line-horz", | ||||
|                                         "title": "Border color", | ||||
|                                         "description": "Set border color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "color", | ||||
|                                         "cssclass": "icon-T", | ||||
|                                         "cssClass": "icon-T", | ||||
|                                         "title": "Text color", | ||||
|                                         "description": "Set text color", | ||||
|                                         "mandatory": true, | ||||
| @@ -132,20 +132,20 @@ define([ | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "url", | ||||
|                                         "cssclass": "icon-image", | ||||
|                                         "cssClass": "icon-image", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Image Properties", | ||||
|                                         "description": "Edit image properties", | ||||
|                                         "dialog": { | ||||
|                                             "control": "textfield", | ||||
|                                             "name": "Image URL", | ||||
|                                             "cssclass": "l-input-lg", | ||||
|                                             "cssClass": "l-input-lg", | ||||
|                                             "required": true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "text", | ||||
|                                         "cssclass": "icon-gear", | ||||
|                                         "cssClass": "icon-gear", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Text Properties", | ||||
|                                         "description": "Edit text properties", | ||||
| @@ -157,14 +157,14 @@ define([ | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "showTitle", | ||||
|                                         "cssclass": "icon-two-parts-both", | ||||
|                                         "cssClass": "icon-two-parts-both", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Show title", | ||||
|                                         "description": "Show telemetry element title" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "hideTitle", | ||||
|                                         "cssclass": "icon-two-parts-one-only", | ||||
|                                         "cssClass": "icon-two-parts-one-only", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Hide title", | ||||
|                                         "description": "Hide telemetry element title" | ||||
| @@ -176,7 +176,7 @@ define([ | ||||
|                                     { | ||||
|                                         "method": "remove", | ||||
|                                         "control": "button", | ||||
|                                         "cssclass": "icon-trash" | ||||
|                                         "cssClass": "icon-trash" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             } | ||||
| @@ -188,7 +188,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "telemetry.fixed", | ||||
|                     "name": "Fixed Position Display", | ||||
|                     "cssclass": "icon-box-with-dashed-lines", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "description": "Collect and display telemetry elements in " + | ||||
|                     "alphanumeric format in a simple canvas workspace. " + | ||||
|                     "Elements can be positioned and sized. " + | ||||
| @@ -215,12 +215,12 @@ define([ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define([ | ||||
|                 { | ||||
|                     "name": "Imagery", | ||||
|                     "key": "imagery", | ||||
|                     "cssclass": "icon-image", | ||||
|                     "cssClass": "icon-image", | ||||
|                     "template": imageryTemplate, | ||||
|                     "priority": "preferred", | ||||
|                     "needs": [ | ||||
|   | ||||
| @@ -56,7 +56,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "layout", | ||||
|                     "name": "Display Layout", | ||||
|                     "cssclass": "icon-layout", | ||||
|                     "cssClass": "icon-layout", | ||||
|                     "type": "layout", | ||||
|                     "template": layoutTemplate, | ||||
|                     "editable": true, | ||||
| @@ -65,7 +65,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "fixed", | ||||
|                     "name": "Fixed Position", | ||||
|                     "cssclass": "icon-box-with-dashed-lines", | ||||
|                     "cssClass": "icon-box-with-dashed-lines", | ||||
|                     "type": "telemetry.panel", | ||||
|                     "template": fixedTemplate, | ||||
|                     "uses": [ | ||||
| @@ -77,7 +77,7 @@ define([ | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "add", | ||||
|                                         "cssclass": "icon-plus", | ||||
|                                         "cssClass": "icon-plus", | ||||
|                                         "control": "menu-button", | ||||
|                                         "text": "Add", | ||||
|                                         "title": "Add", | ||||
| @@ -85,22 +85,22 @@ define([ | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Box", | ||||
|                                                 "cssclass": "icon-box", | ||||
|                                                 "cssClass": "icon-box", | ||||
|                                                 "key": "fixed.box" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Line", | ||||
|                                                 "cssclass": "icon-line-horz", | ||||
|                                                 "cssClass": "icon-line-horz", | ||||
|                                                 "key": "fixed.line" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Text", | ||||
|                                                 "cssclass": "icon-T", | ||||
|                                                 "cssClass": "icon-T", | ||||
|                                                 "key": "fixed.text" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Image", | ||||
|                                                 "cssclass": "icon-image", | ||||
|                                                 "cssClass": "icon-image", | ||||
|                                                 "key": "fixed.image" | ||||
|                                             } | ||||
|                                         ] | ||||
| @@ -111,50 +111,50 @@ define([ | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "method": "order", | ||||
|                                         "cssclass": "icon-layers", | ||||
|                                         "cssClass": "icon-layers", | ||||
|                                         "control": "menu-button", | ||||
|                                         "title": "Layering", | ||||
|                                         "description": "Move the selected object above or below other objects", | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Move to Top", | ||||
|                                                 "cssclass": "icon-arrow-double-up", | ||||
|                                                 "cssClass": "icon-arrow-double-up", | ||||
|                                                 "key": "top" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Up", | ||||
|                                                 "cssclass": "icon-arrow-up", | ||||
|                                                 "cssClass": "icon-arrow-up", | ||||
|                                                 "key": "up" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move Down", | ||||
|                                                 "cssclass": "icon-arrow-down", | ||||
|                                                 "cssClass": "icon-arrow-down", | ||||
|                                                 "key": "down" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Move to Bottom", | ||||
|                                                 "cssclass": "icon-arrow-double-down", | ||||
|                                                 "cssClass": "icon-arrow-double-down", | ||||
|                                                 "key": "bottom" | ||||
|                                             } | ||||
|                                         ] | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "fill", | ||||
|                                         "cssclass": "icon-paint-bucket", | ||||
|                                         "cssClass": "icon-paint-bucket", | ||||
|                                         "title": "Fill color", | ||||
|                                         "description": "Set fill color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "stroke", | ||||
|                                         "cssclass": "icon-line-horz", | ||||
|                                         "cssClass": "icon-line-horz", | ||||
|                                         "title": "Border color", | ||||
|                                         "description": "Set border color", | ||||
|                                         "control": "color" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "color", | ||||
|                                         "cssclass": "icon-T", | ||||
|                                         "cssClass": "icon-T", | ||||
|                                         "title": "Text color", | ||||
|                                         "description": "Set text color", | ||||
|                                         "mandatory": true, | ||||
| @@ -162,20 +162,20 @@ define([ | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "url", | ||||
|                                         "cssclass": "icon-image", | ||||
|                                         "cssClass": "icon-image", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Image Properties", | ||||
|                                         "description": "Edit image properties", | ||||
|                                         "dialog": { | ||||
|                                             "control": "textfield", | ||||
|                                             "name": "Image URL", | ||||
|                                             "cssclass": "l-input-lg", | ||||
|                                             "cssClass": "l-input-lg", | ||||
|                                             "required": true | ||||
|                                         } | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "property": "text", | ||||
|                                         "cssclass": "icon-gear", | ||||
|                                         "cssClass": "icon-gear", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "title": "Text Properties", | ||||
|                                         "description": "Edit text properties", | ||||
| @@ -187,14 +187,14 @@ define([ | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "showTitle", | ||||
|                                         "cssclass": "icon-two-parts-both", | ||||
|                                         "cssClass": "icon-two-parts-both", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Show title", | ||||
|                                         "description": "Show telemetry element title" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "method": "hideTitle", | ||||
|                                         "cssclass": "icon-two-parts-one-only", | ||||
|                                         "cssClass": "icon-two-parts-one-only", | ||||
|                                         "control": "button", | ||||
|                                         "title": "Hide title", | ||||
|                                         "description": "Hide telemetry element title" | ||||
| @@ -206,7 +206,7 @@ define([ | ||||
|                                     { | ||||
|                                         "method": "remove", | ||||
|                                         "control": "button", | ||||
|                                         "cssclass": "icon-trash", | ||||
|                                         "cssClass": "icon-trash", | ||||
|                                         "title": "Delete", | ||||
|                                         "description": "Delete the selected item" | ||||
|                                     } | ||||
| @@ -275,7 +275,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "layout", | ||||
|                     "name": "Display Layout", | ||||
|                     "cssclass": "icon-layout", | ||||
|                     "cssClass": "icon-layout", | ||||
|                     "description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.", | ||||
|                     "priority": 900, | ||||
|                     "features": "creation", | ||||
| @@ -291,12 +291,12 @@ define([ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "key": "layoutGrid", | ||||
| @@ -307,7 +307,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "telemetry.panel", | ||||
|                     "name": "Telemetry Panel", | ||||
|                     "cssclass": "icon-telemetry-panel", | ||||
|                     "cssClass": "icon-telemetry-panel", | ||||
|                     "description": "A panel for collecting telemetry elements.", | ||||
|                     "priority": 899, | ||||
|                     "delegates": [ | ||||
| @@ -330,12 +330,12 @@ define([ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssclass": "l-input-sm l-numeric" | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|   | ||||
| @@ -39,7 +39,7 @@ define( | ||||
|                 candidate && | ||||
|                 context && | ||||
|                 candidate.instanceOf('layout') && | ||||
|                 context.instanceOf('folder'); | ||||
|                 context.getCapability('type').instanceOf('folder'); | ||||
|             return !isFolderInLayout; | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,7 @@ define( | ||||
|                                     key: "url", | ||||
|                                     control: "textfield", | ||||
|                                     name: "Image URL", | ||||
|                                     "cssclass": "l-input-lg", | ||||
|                                     "cssClass": "l-input-lg", | ||||
|                                     required: true | ||||
|                                 } | ||||
|                             ] | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "example.page", | ||||
|                     "name": "Web Page", | ||||
|                     "cssclass": "icon-page", | ||||
|                     "cssClass": "icon-page", | ||||
|                     "description": "Embed a web page or web-based image in a resizeable window component. Can be added to Display Layouts. Note that the URL being embedded must allow iframing.", | ||||
|                     "priority": 50, | ||||
|                     "features": [ | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                             "control": "textfield", | ||||
|                             "pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$", | ||||
|                             "required": true, | ||||
|                             "cssclass": "l-input-lg" | ||||
|                             "cssClass": "l-input-lg" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|   | ||||
| @@ -47,7 +47,7 @@ define([ | ||||
|                 { | ||||
|                     "name": "Plot", | ||||
|                     "key": "plot", | ||||
|                     "cssclass": "icon-sine", | ||||
|                     "cssClass": "icon-sine", | ||||
|                     "template": plotTemplate, | ||||
|                     "needs": [ | ||||
|                         "telemetry" | ||||
|   | ||||
| @@ -121,7 +121,7 @@ | ||||
|                            ng-show="plot.isZoomed()" | ||||
|                            title="Reset pan/zoom"> | ||||
|                         </a> | ||||
|                         <div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssclass}}" | ||||
|                         <div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssClass}}" | ||||
|                              ng-if="plot.getModeOptions().length > 1" | ||||
|                              ng-controller="ClickAwayController as toggle"> | ||||
|                             <span class="l-click-area" ng-click="toggle.toggle()"></span> | ||||
| @@ -130,7 +130,7 @@ | ||||
|                                 <ul> | ||||
|                                     <li ng-repeat="option in plot.getModeOptions()" | ||||
|                                         ng-click="plot.setMode(option); toggle.setState(false)" | ||||
|                                         class="{{option.cssclass}}"> | ||||
|                                         class="{{option.cssClass}}"> | ||||
|                                         {{option.name}} | ||||
|                                     </li> | ||||
|                                 </ul> | ||||
|   | ||||
| @@ -217,8 +217,8 @@ define( | ||||
|                 if (handle) { | ||||
|                     handle.unsubscribe(); | ||||
|                     handle = undefined; | ||||
|                     conductor.off("timeOfInterest", changeTimeOfInterest); | ||||
|                 } | ||||
|                 conductor.off("timeOfInterest", changeTimeOfInterest); | ||||
|             } | ||||
|  | ||||
|             function requery() { | ||||
| @@ -352,7 +352,7 @@ define( | ||||
|  | ||||
|         /** | ||||
|          * Get the current mode that is applicable to this plot. This | ||||
|          * will include key, name, and cssclass fields. | ||||
|          * will include key, name, and cssClass fields. | ||||
|          */ | ||||
|         PlotController.prototype.getMode = function () { | ||||
|             return this.modeOptions.getMode(); | ||||
|   | ||||
| @@ -27,13 +27,13 @@ define( | ||||
|         var STACKED = { | ||||
|                 key: "stacked", | ||||
|                 name: "Stacked", | ||||
|                 cssclass: "icon-plot-stacked", | ||||
|                 cssClass: "icon-plot-stacked", | ||||
|                 Constructor: PlotStackMode | ||||
|             }, | ||||
|             OVERLAID = { | ||||
|                 key: "overlaid", | ||||
|                 name: "Overlaid", | ||||
|                 cssclass: "icon-plot-overlay", | ||||
|                 cssClass: "icon-plot-overlay", | ||||
|                 Constructor: PlotOverlayMode | ||||
|             }; | ||||
|  | ||||
| @@ -115,7 +115,7 @@ define( | ||||
|  | ||||
|         /** | ||||
|          * Get all mode options available for each plot. Each | ||||
|          * mode contains a `name` and `cssclass` field suitable | ||||
|          * mode contains a `name` and `cssClass` field suitable | ||||
|          * for display in a template. | ||||
|          * @return {Array} the available modes | ||||
|          */ | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "static.markup", | ||||
|                     "name": "Static Markup", | ||||
|                     "cssclass": "icon-pencil", | ||||
|                     "cssClass": "icon-pencil", | ||||
|                     "description": "Static markup sandbox", | ||||
|                     "features": [ | ||||
|                         "creation" | ||||
|   | ||||
| @@ -22,25 +22,21 @@ | ||||
|  | ||||
| define([ | ||||
|     "./src/directives/MCTTable", | ||||
|     "./src/controllers/RealtimeTableController", | ||||
|     "./src/controllers/HistoricalTableController", | ||||
|     "./src/controllers/TelemetryTableController", | ||||
|     "./src/controllers/TableOptionsController", | ||||
|     '../../commonUI/regions/src/Region', | ||||
|     '../../commonUI/browse/src/InspectorRegion', | ||||
|     "text!./res/templates/table-options-edit.html", | ||||
|     "text!./res/templates/rt-table.html", | ||||
|     "text!./res/templates/historical-table.html", | ||||
|     "text!./res/templates/telemetry-table.html", | ||||
|     "legacyRegistry" | ||||
| ], function ( | ||||
|     MCTTable, | ||||
|     RealtimeTableController, | ||||
|     HistoricalTableController, | ||||
|     TelemetryTableController, | ||||
|     TableOptionsController, | ||||
|     Region, | ||||
|     InspectorRegion, | ||||
|     tableOptionsEditTemplate, | ||||
|     rtTableTemplate, | ||||
|     historicalTableTemplate, | ||||
|     telemetryTableTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     /** | ||||
| @@ -65,9 +61,9 @@ define([ | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "key": "table", | ||||
|                     "name": "Historical Telemetry Table", | ||||
|                     "cssclass": "icon-tabular", | ||||
|                     "description": "A static table of all values over time for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. The number of rows is based on the range of your query. New incoming data must be manually re-queried for.", | ||||
|                     "name": "Telemetry Table", | ||||
|                     "cssClass": "icon-tabular-realtime", | ||||
|                     "description": "A table of values over a given time period. The table will be automatically updated with new values as they become available", | ||||
|                     "priority": 861, | ||||
|                     "features": "creation", | ||||
|                     "delegates": [ | ||||
| @@ -85,42 +81,13 @@ define([ | ||||
|                     "views": [ | ||||
|                         "table" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "rttable", | ||||
|                     "name": "Real-time Telemetry Table", | ||||
|                     "cssclass": "icon-tabular-realtime", | ||||
|                     "description": "A scrolling table of latest values for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. New incoming data is automatically added to the view.", | ||||
|                     "priority": 860, | ||||
|                     "features": "creation", | ||||
|                     "delegates": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|                     "inspector": tableInspector, | ||||
|                     "contains": [ | ||||
|                         { | ||||
|                             "has": "telemetry" | ||||
|                         } | ||||
|                     ], | ||||
|                     "model": { | ||||
|                         "composition": [] | ||||
|                     }, | ||||
|                     "views": [ | ||||
|                         "rt-table", | ||||
|                         "scrolling-table" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "HistoricalTableController", | ||||
|                     "implementation": HistoricalTableController, | ||||
|                     "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "openmct"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "RealtimeTableController", | ||||
|                     "implementation": RealtimeTableController, | ||||
|                     "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "openmct"] | ||||
|                     "key": "TelemetryTableController", | ||||
|                     "implementation": TelemetryTableController, | ||||
|                     "depends": ["$scope", "$timeout", "openmct"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "TableOptionsController", | ||||
| @@ -131,21 +98,10 @@ define([ | ||||
|             ], | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "name": "Historical Table", | ||||
|                     "name": "Telemetry Table", | ||||
|                     "key": "table", | ||||
|                     "template": historicalTableTemplate, | ||||
|                     "cssclass": "icon-tabular", | ||||
|                     "needs": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|                     "delegation": true, | ||||
|                     "editable": false | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Real-time Table", | ||||
|                     "key": "rt-table", | ||||
|                     "cssclass": "icon-tabular-realtime", | ||||
|                     "template": rtTableTemplate, | ||||
|                     "cssClass": "icon-tabular-realtime", | ||||
|                     "template": telemetryTableTemplate, | ||||
|                     "needs": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|   | ||||
| @@ -49,7 +49,7 @@ | ||||
|             </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|             <tr ng-repeat-start="visibleRow in visibleRows track by visibleRow.rowIndex" | ||||
|             <tr ng-repeat-start="visibleRow in visibleRows track by $index" | ||||
|                 ng-if="visibleRow.rowIndex === toiRowIndex" | ||||
|                 ng-style="{ top: visibleRow.offsetY + 'px' }" | ||||
|                 class="l-toi-tablerow"> | ||||
| @@ -62,7 +62,7 @@ | ||||
|                 ng-style="{ top: visibleRow.offsetY + 'px' }" | ||||
|                 ng-click="table.onRowClick($event, visibleRow.rowIndex) "> | ||||
|                 <td ng-repeat="header in displayHeaders" | ||||
|                     ng-style=" { | ||||
|                     ng-style="{ | ||||
|                         width: columnWidths[$index] + 'px', | ||||
|                         'max-width': columnWidths[$index] + 'px', | ||||
|                     }" | ||||
|   | ||||
| @@ -1,12 +0,0 @@ | ||||
| <div ng-controller="RealtimeTableController as tableController"> | ||||
|     <mct-table | ||||
|         headers="headers" | ||||
|         rows="rows" | ||||
|         time-columns="tableController.timeColumns" | ||||
|         enableFilter="true" | ||||
|         enableSort="true" | ||||
|         class="tabular-holder has-control-bar" | ||||
|         sort-column="defaultSort" | ||||
|         auto-scroll="true"> | ||||
|     </mct-table> | ||||
| </div> | ||||
| @@ -1,12 +1,14 @@ | ||||
| <div ng-controller="HistoricalTableController as tableController" | ||||
| <div ng-controller="TelemetryTableController as tableController" | ||||
|      ng-class="{'loading': loading}"> | ||||
|     <mct-table | ||||
|         headers="headers" | ||||
|         time-columns="tableController.timeColumns" | ||||
|         rows="rows" | ||||
|         time-columns="tableController.timeColumns" | ||||
|         format-cell="formatCell" | ||||
|         enableFilter="true" | ||||
|         enableSort="true" | ||||
|         sort-column="defaultSort" | ||||
|         auto-scroll="autoScroll" | ||||
|         default-sort="defaultSort" | ||||
|         class="tabular-holder has-control-bar"> | ||||
|     </mct-table> | ||||
| </div> | ||||
| @@ -1,62 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining DomainColumn. | ||||
|  */ | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * A column which will report telemetry domain values | ||||
|          * (typically, timestamps.) Used by the ScrollingListController. | ||||
|          * | ||||
|          * @memberof platform/features/table | ||||
|          * @constructor | ||||
|          * @param domainMetadata an object with the machine- and human- | ||||
|          *        readable names for this domain (in `key` and `name` | ||||
|          *        fields, respectively.) | ||||
|          * @param {TelemetryFormatter} telemetryFormatter the telemetry | ||||
|          *        formatting service, for making values human-readable. | ||||
|          */ | ||||
|         function DomainColumn(domainMetadata, telemetryFormatter) { | ||||
|             this.domainMetadata = domainMetadata; | ||||
|             this.telemetryFormatter = telemetryFormatter; | ||||
|         } | ||||
|  | ||||
|         DomainColumn.prototype.getTitle = function () { | ||||
|             return this.domainMetadata.name; | ||||
|         }; | ||||
|  | ||||
|         DomainColumn.prototype.getValue = function (domainObject, datum) { | ||||
|             return { | ||||
|                 text: this.telemetryFormatter.formatDomainValue( | ||||
|                     datum[this.domainMetadata.key], | ||||
|                     this.domainMetadata.format | ||||
|                 ) | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         return DomainColumn; | ||||
|     } | ||||
| ); | ||||
| @@ -1,52 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining NameColumn. Created by vwoeltje on 11/18/14. | ||||
|  */ | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * A column which will report the name of the domain object | ||||
|          * which exposed specific telemetry values. | ||||
|          * | ||||
|          * @memberof platform/features/table | ||||
|          * @constructor | ||||
|          */ | ||||
|         function NameColumn() { | ||||
|         } | ||||
|  | ||||
|         NameColumn.prototype.getTitle = function () { | ||||
|             return "Name"; | ||||
|         }; | ||||
|  | ||||
|         NameColumn.prototype.getValue = function (domainObject) { | ||||
|             return { | ||||
|                 text: domainObject.getModel().name | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         return NameColumn; | ||||
|     } | ||||
| ); | ||||
| @@ -1,65 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * Module defining DomainColumn. Created by vwoeltje on 11/18/14. | ||||
|  */ | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * A column which will report telemetry range values | ||||
|          * (typically, measurements.) Used by the ScrollingListController. | ||||
|          * | ||||
|          * @memberof platform/features/table | ||||
|          * @constructor | ||||
|          * @param rangeMetadata an object with the machine- and human- | ||||
|          *        readable names for this range (in `key` and `name` | ||||
|          *        fields, respectively.) | ||||
|          * @param {TelemetryFormatter} telemetryFormatter the telemetry | ||||
|          *        formatting service, for making values human-readable. | ||||
|          */ | ||||
|         function RangeColumn(rangeMetadata, telemetryFormatter) { | ||||
|             this.rangeMetadata = rangeMetadata; | ||||
|             this.telemetryFormatter = telemetryFormatter; | ||||
|         } | ||||
|  | ||||
|         RangeColumn.prototype.getTitle = function () { | ||||
|             return this.rangeMetadata.name; | ||||
|         }; | ||||
|  | ||||
|         RangeColumn.prototype.getValue = function (domainObject, datum) { | ||||
|             var range = this.rangeMetadata.key, | ||||
|                 limit = domainObject.getCapability('limit'), | ||||
|                 value = isNaN(datum[range]) ? datum[range] : parseFloat(datum[range]), | ||||
|                 alarm = limit && limit.evaluate(datum, range); | ||||
|  | ||||
|             return { | ||||
|                 cssClass: alarm && alarm.cssClass, | ||||
|                 text: typeof (value) === 'undefined' ? undefined : this.telemetryFormatter.formatRangeValue(value) | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         return RangeColumn; | ||||
|     } | ||||
| ); | ||||
| @@ -21,12 +21,8 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         './DomainColumn', | ||||
|         './RangeColumn', | ||||
|         './NameColumn' | ||||
|     ], | ||||
|     function (DomainColumn, RangeColumn, NameColumn) { | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Class that manages table metadata, state, and contents. | ||||
| @@ -34,10 +30,10 @@ define( | ||||
|          * @param domainObject | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TableConfiguration(domainObject, telemetryFormatter) { | ||||
|         function TableConfiguration(domainObject, openmct) { | ||||
|             this.domainObject = domainObject; | ||||
|             this.columns = []; | ||||
|             this.telemetryFormatter = telemetryFormatter; | ||||
|             this.openmct = openmct; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -47,61 +43,51 @@ define( | ||||
|          */ | ||||
|         TableConfiguration.prototype.populateColumns = function (metadata) { | ||||
|             var self = this; | ||||
|             var telemetryApi = this.openmct.telemetry; | ||||
|  | ||||
|             this.columns = []; | ||||
|  | ||||
|             if (metadata) { | ||||
|  | ||||
|                 metadata.forEach(function (metadatum) { | ||||
|                     //Push domains first | ||||
|                     (metadatum.domains || []).forEach(function (domainMetadata) { | ||||
|                         self.addColumn(new DomainColumn(domainMetadata, | ||||
|                             self.telemetryFormatter)); | ||||
|                     }); | ||||
|                     (metadatum.ranges || []).forEach(function (rangeMetadata) { | ||||
|                         self.addColumn(new RangeColumn(rangeMetadata, | ||||
|                             self.telemetryFormatter)); | ||||
|                     var formatter = telemetryApi.getValueFormatter(metadatum); | ||||
|  | ||||
|                     self.columns.push({ | ||||
|                         getKey: function () { | ||||
|                             return metadatum.key; | ||||
|                         }, | ||||
|                         getTitle: function () { | ||||
|                             return metadatum.name; | ||||
|                         }, | ||||
|                         getValue: function (telemetryDatum, limitEvaluator) { | ||||
|                             var isValueColumn = !!(metadatum.hints.y || metadatum.hints.range); | ||||
|                             var alarm = isValueColumn && | ||||
|                                         limitEvaluator && | ||||
|                                         limitEvaluator.evaluate(telemetryDatum, metadatum); | ||||
|                             var value = { | ||||
|                                 text: formatter ? formatter.format(telemetryDatum[metadatum.key]) | ||||
|                                     : telemetryDatum[metadatum.key], | ||||
|                                 value: telemetryDatum[metadatum.key] | ||||
|                             }; | ||||
|  | ||||
|                             if (alarm) { | ||||
|                                 value.cssClass = alarm.cssClass; | ||||
|                             } | ||||
|                             return value; | ||||
|                         } | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 if (this.columns.length > 0) { | ||||
|                     self.addColumn(new NameColumn(), 0); | ||||
|                 } | ||||
|             } | ||||
|             return this; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Add a column definition to this Table | ||||
|          * @param {RangeColumn | DomainColumn | NameColumn} column | ||||
|          * @param {Number} [index] Where the column should appear (will be | ||||
|          * affected by column filtering) | ||||
|          */ | ||||
|         TableConfiguration.prototype.addColumn = function (column, index) { | ||||
|             if (typeof index === 'undefined') { | ||||
|                 this.columns.push(column); | ||||
|             } else { | ||||
|                 this.columns.splice(index, 0, column); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          * @param column | ||||
|          * @returns {*|string} | ||||
|          */ | ||||
|         TableConfiguration.prototype.getColumnTitle = function (column) { | ||||
|             return column.getTitle(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Get a simple list of column titles | ||||
|          * @returns {Array} The titles of the columns | ||||
|          */ | ||||
|         TableConfiguration.prototype.getHeaders = function () { | ||||
|             var self = this; | ||||
|             return this.columns.map(function (column, i) { | ||||
|                 return self.getColumnTitle(column) || 'Column ' + (i + 1); | ||||
|                 return column.getTitle() || 'Column ' + (i + 1); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
| @@ -113,17 +99,16 @@ define( | ||||
|          * @returns {Object} Key value pairs where the key is the column | ||||
|          * title, and the value is the formatted value from the provided datum. | ||||
|          */ | ||||
|         TableConfiguration.prototype.getRowValues = function (telemetryObject, datum) { | ||||
|             var self = this; | ||||
|         TableConfiguration.prototype.getRowValues = function (limitEvaluator, datum) { | ||||
|             return this.columns.reduce(function (rowObject, column, i) { | ||||
|                 var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1), | ||||
|                     columnValue = column.getValue(telemetryObject, datum); | ||||
|                 var columnTitle = column.getTitle() || 'Column ' + (i + 1), | ||||
|                     columnValue = column.getValue(datum, limitEvaluator); | ||||
|  | ||||
|                 if (columnValue !== undefined && columnValue.text === undefined) { | ||||
|                     columnValue.text = ''; | ||||
|                 } | ||||
|                 // Don't replace something with nothing. | ||||
|                 // This occurs when there are multiple columns with the | ||||
|                 // This occurs when there are multiple columns with the same | ||||
|                 // column title | ||||
|                 if (rowObject[columnTitle] === undefined || | ||||
|                     rowObject[columnTitle].text === undefined || | ||||
| @@ -187,7 +172,9 @@ define( | ||||
|             }); | ||||
|  | ||||
|             //Synchronize column configuration with model | ||||
|             if (configChanged(configuration, defaultConfig)) { | ||||
|             if (this.domainObject.hasCapability('editor') && | ||||
|                 this.domainObject.getCapability('editor').isEditContextRoot() && | ||||
|                 configChanged(configuration, defaultConfig)) { | ||||
|                 this.saveColumnConfiguration(configuration); | ||||
|             } | ||||
|  | ||||
|   | ||||
							
								
								
									
										255
									
								
								platform/features/table/src/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								platform/features/table/src/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'lodash', | ||||
|         'EventEmitter' | ||||
|     ], | ||||
|     function (_, EventEmitter) { | ||||
|  | ||||
|         /** | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TelemetryCollection() { | ||||
|             EventEmitter.call(this, arguments); | ||||
|             this.telemetry = []; | ||||
|             this.highBuffer = []; | ||||
|             this.sortField = undefined; | ||||
|             this.lastBounds = {}; | ||||
|  | ||||
|             _.bindAll(this, [ | ||||
|                 'addOne', | ||||
|                 'iteratee' | ||||
|             ]); | ||||
|         } | ||||
|  | ||||
|         TelemetryCollection.prototype = Object.create(EventEmitter.prototype); | ||||
|  | ||||
|         TelemetryCollection.prototype.iteratee = function (item) { | ||||
|             return _.get(item, this.sortField); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * This function is optimized for ticking - it assumes that start and end | ||||
|          * bounds will only increase and as such this cannot be used for decreasing | ||||
|          * bounds changes. | ||||
|          * | ||||
|          * An implication of this is that data will not be discarded that exceeds | ||||
|          * the given end bounds. For arbitrary bounds changes, it's assumed that | ||||
|          * a telemetry requery is performed anyway, and the collection is cleared | ||||
|          * and repopulated. | ||||
|          * | ||||
|          * @fires TelemetryCollection#added | ||||
|          * @fires TelemetryCollection#discarded | ||||
|          * @param bounds | ||||
|          */ | ||||
|         TelemetryCollection.prototype.bounds = function (bounds) { | ||||
|             var startChanged = this.lastBounds.start !== bounds.start; | ||||
|             var endChanged = this.lastBounds.end !== bounds.end; | ||||
|             var startIndex = 0; | ||||
|             var endIndex = 0; | ||||
|             var discarded; | ||||
|             var added; | ||||
|             var testValue; | ||||
|  | ||||
|             // If collection is not sorted by a time field, we cannot respond to | ||||
|             // bounds events | ||||
|             if (this.sortField === undefined) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (startChanged) { | ||||
|                 testValue = _.set({}, this.sortField, bounds.start); | ||||
|                 // Calculate the new index of the first item within the bounds | ||||
|                 startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField); | ||||
|                 discarded = this.telemetry.splice(0, startIndex); | ||||
|             } | ||||
|             if (endChanged) { | ||||
|                 testValue = _.set({}, this.sortField, bounds.end); | ||||
|                 // Calculate the new index of the last item in bounds | ||||
|                 endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField); | ||||
|                 added = this.highBuffer.splice(0, endIndex); | ||||
|                 this.telemetry = this.telemetry.concat(added); | ||||
|             } | ||||
|  | ||||
|             if (discarded && discarded.length > 0) { | ||||
|                 /** | ||||
|                  * A `discarded` event is thrown when telemetry data fall out of | ||||
|                  * bounds due to a bounds change event | ||||
|                  * @type {object[]} discarded the telemetry data | ||||
|                  * discarded as a result of the bounds change | ||||
|                  */ | ||||
|                 this.emit('discarded', discarded); | ||||
|             } | ||||
|             if (added && added.length > 0) { | ||||
|                 /** | ||||
|                  * An `added` event is thrown when a bounds change results in | ||||
|                  * received telemetry falling within the new bounds. | ||||
|                  * @type {object[]} added the telemetry data that is now within bounds | ||||
|                  */ | ||||
|                 this.emit('added', added); | ||||
|             } | ||||
|             this.lastBounds = bounds; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Determines is a given telemetry datum is within the bounds currently | ||||
|          * defined for this telemetry collection. | ||||
|          * @private | ||||
|          * @param datum | ||||
|          * @returns {boolean} | ||||
|          */ | ||||
|         TelemetryCollection.prototype.inBounds = function (datum) { | ||||
|             var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined); | ||||
|             var withinBounds = | ||||
|                 _.get(datum, this.sortField) >= this.lastBounds.start && | ||||
|                 _.get(datum, this.sortField) <= this.lastBounds.end; | ||||
|  | ||||
|             return noBoundsDefined || withinBounds; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Adds an individual item to the collection. Used internally only | ||||
|          * @private | ||||
|          * @param item | ||||
|          */ | ||||
|         TelemetryCollection.prototype.addOne = function (item) { | ||||
|             var isDuplicate = false; | ||||
|             var boundsDefined = this.lastBounds && | ||||
|                 (this.lastBounds.start !== undefined && this.lastBounds.end !== undefined); | ||||
|             var array; | ||||
|             var boundsLow; | ||||
|             var boundsHigh; | ||||
|  | ||||
|             // If collection is not sorted by a time field, we cannot respond to | ||||
|             // bounds events, so no bounds checking necessary | ||||
|             if (this.sortField === undefined) { | ||||
|                 this.telemetry.push(item); | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             // Insert into either in-bounds array, or the out of bounds high buffer. | ||||
|             // Data in the high buffer will be re-evaluated for possible insertion on next tick | ||||
|  | ||||
|             if (boundsDefined) { | ||||
|                 boundsHigh = _.get(item, this.sortField) > this.lastBounds.end; | ||||
|                 boundsLow = _.get(item, this.sortField) < this.lastBounds.start; | ||||
|  | ||||
|                 if (!boundsHigh && !boundsLow) { | ||||
|                     array = this.telemetry; | ||||
|                 } else if (boundsHigh) { | ||||
|                     array = this.highBuffer; | ||||
|                 } | ||||
|             } else { | ||||
|                 array = this.telemetry; | ||||
|             } | ||||
|  | ||||
|             // If out of bounds low, disregard data | ||||
|             if (!boundsLow) { | ||||
|  | ||||
|                 // Going to check for duplicates. Bound the search problem to | ||||
|                 // items around the given time. Use sortedIndex because it | ||||
|                 // employs a binary search which is O(log n). Can use binary search | ||||
|                 // based on time stamp because the array is guaranteed ordered due | ||||
|                 // to sorted insertion. | ||||
|                 var startIx = _.sortedIndex(array, item, this.sortField); | ||||
|  | ||||
|                 if (startIx !== array.length) { | ||||
|                     var endIx = _.sortedLastIndex(array, item, this.sortField); | ||||
|  | ||||
|                     // Create an array of potential dupes, based on having the | ||||
|                     // same time stamp | ||||
|                     var potentialDupes = array.slice(startIx, endIx + 1); | ||||
|                     // Search potential dupes for exact dupe | ||||
|                     isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1; | ||||
|                 } | ||||
|  | ||||
|                 if (!isDuplicate) { | ||||
|                     array.splice(startIx, 0, item); | ||||
|  | ||||
|                     //Return true if it was added and in bounds | ||||
|                     return array === this.telemetry; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Add an array of objects to this telemetry collection | ||||
|          * @fires TelemetryCollection#added | ||||
|          * @param {object[]} items | ||||
|          */ | ||||
|         TelemetryCollection.prototype.add = function (items) { | ||||
|             var added = items.filter(this.addOne); | ||||
|             this.emit('added', added); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Clears the contents of the telemetry collection | ||||
|          */ | ||||
|         TelemetryCollection.prototype.clear = function () { | ||||
|             this.telemetry = []; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Sorts the telemetry collection based on the provided sort field | ||||
|          * specifier. | ||||
|          * @example | ||||
|          * // First build some mock telemetry for the purpose of an example | ||||
|          * let now = Date.now(); | ||||
|          * let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) { | ||||
|          *     return { | ||||
|          *         // define an object property to demonstrate nested paths | ||||
|          *         timestamp: { | ||||
|          *             ms: now - value * 1000, | ||||
|          *             text: | ||||
|          *         }, | ||||
|          *         value: value | ||||
|          *     } | ||||
|          * }); | ||||
|          * let collection = new TelemetryCollection(); | ||||
|          * | ||||
|          * collection.add(telemetry); | ||||
|          * | ||||
|          * // Sort by telemetry value | ||||
|          * collection.sort("value"); | ||||
|          * | ||||
|          * // Sort by ms since epoch | ||||
|          * collection.sort("timestamp.ms"); | ||||
|          * | ||||
|          * // Sort by formatted date text | ||||
|          * collection.sort("timestamp.text"); | ||||
|          * | ||||
|          * | ||||
|          * @param {string} sortField An object property path. | ||||
|          */ | ||||
|         TelemetryCollection.prototype.sort = function (sortField) { | ||||
|             this.sortField = sortField; | ||||
|             if (sortField !== undefined) { | ||||
|                 this.telemetry = _.sortBy(this.telemetry, this.iteratee); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return TelemetryCollection; | ||||
|     } | ||||
| ); | ||||
| @@ -1,141 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         './TelemetryTableController' | ||||
|     ], | ||||
|     function (TableController) { | ||||
|         var BATCH_SIZE = 1000; | ||||
|  | ||||
|         /** | ||||
|          * Extends TelemetryTableController and adds real-time streaming | ||||
|          * support. | ||||
|          * @memberof platform/features/table | ||||
|          * @param $scope | ||||
|          * @param telemetryHandler | ||||
|          * @param telemetryFormatter | ||||
|          * @constructor | ||||
|          */ | ||||
|         function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout, openmct) { | ||||
|             var self = this; | ||||
|  | ||||
|             this.$timeout = $timeout; | ||||
|             this.timeoutHandle = undefined; | ||||
|             this.batchSize = BATCH_SIZE; | ||||
|  | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 if (self.timeoutHandle) { | ||||
|                     self.$timeout.cancel(self.timeoutHandle); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             TableController.call(this, $scope, telemetryHandler, telemetryFormatter, openmct); | ||||
|         } | ||||
|  | ||||
|         HistoricalTableController.prototype = Object.create(TableController.prototype); | ||||
|  | ||||
|         /** | ||||
|          * Set provided row data on scope, and cancel loading spinner | ||||
|          * @private | ||||
|          */ | ||||
|         HistoricalTableController.prototype.doneProcessing = function (rowData) { | ||||
|             this.$scope.rows = rowData; | ||||
|             this.$scope.loading = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         HistoricalTableController.prototype.registerChangeListeners = function () { | ||||
|             TableController.prototype.registerChangeListeners.call(this); | ||||
|             //Change of bounds in time conductor | ||||
|             this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', | ||||
|                     this.boundsChange.bind(this)) | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         HistoricalTableController.prototype.boundsChange = function (event, bounds, follow) { | ||||
|             // If in follow mode, don't bother re-subscribing, data will be | ||||
|             // received from existing subscription. | ||||
|             if (follow !== true) { | ||||
|                 this.subscribe(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Processes an array of objects, formatting the telemetry available | ||||
|          * for them and setting it on scope when done | ||||
|          * @private | ||||
|          */ | ||||
|         HistoricalTableController.prototype.processTelemetryObjects = function (objects, offset, start, rowData) { | ||||
|             var telemetryObject = objects[offset], | ||||
|                 series, | ||||
|                 i = start, | ||||
|                 pointCount, | ||||
|                 end; | ||||
|  | ||||
|             //No more objects to process | ||||
|             if (!telemetryObject) { | ||||
|                 return this.doneProcessing(rowData); | ||||
|             } | ||||
|  | ||||
|             series = this.handle.getSeries(telemetryObject); | ||||
|  | ||||
|             pointCount = series.getPointCount(); | ||||
|             end = Math.min(start + this.batchSize, pointCount); | ||||
|  | ||||
|             //Process rows in a batch with size not exceeding a maximum length | ||||
|             for (; i < end; i++) { | ||||
|                 rowData.push(this.table.getRowValues(telemetryObject, | ||||
|                     this.handle.makeDatum(telemetryObject, series, i))); | ||||
|             } | ||||
|  | ||||
|             //Done processing all rows for this object. | ||||
|             if (end >= pointCount) { | ||||
|                 offset++; | ||||
|                 end = 0; | ||||
|             } | ||||
|  | ||||
|             // Done processing either a batch or an object, yield process | ||||
|             // before continuing processing | ||||
|             this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, objects, offset, end, rowData)); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|         * Populates historical data on scope when it becomes available from | ||||
|         * the telemetry API | ||||
|         */ | ||||
|         HistoricalTableController.prototype.addHistoricalData = function () { | ||||
|             if (this.timeoutHandle) { | ||||
|                 this.$timeout.cancel(this.timeoutHandle); | ||||
|             } | ||||
|  | ||||
|             this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, [])); | ||||
|         }; | ||||
|  | ||||
|         return HistoricalTableController; | ||||
|     } | ||||
| ); | ||||
| @@ -1,7 +1,10 @@ | ||||
|  | ||||
| define( | ||||
|     ['zepto'], | ||||
|     function ($) { | ||||
|     [ | ||||
|         'zepto', | ||||
|         'lodash' | ||||
|     ], | ||||
|     function ($, _) { | ||||
|  | ||||
|         /** | ||||
|          * A controller for the MCTTable directive. Populates scope with | ||||
| @@ -12,13 +15,13 @@ define( | ||||
|          * @param element | ||||
|          * @constructor | ||||
|          */ | ||||
|         function MCTTableController($scope, $timeout, element, exportService, formatService, openmct) { | ||||
|         function MCTTableController($scope, $window, element, exportService, formatService, openmct) { | ||||
|             var self = this; | ||||
|  | ||||
|             this.$scope = $scope; | ||||
|             this.element = $(element[0]); | ||||
|             this.$timeout = $timeout; | ||||
|             this.maxDisplayRows = 50; | ||||
|             this.$window = $window; | ||||
|             this.maxDisplayRows = 100; | ||||
|  | ||||
|             this.scrollable = this.element.find('.l-view-section.scrolling').first(); | ||||
|             this.resultsHeader = this.element.find('.mct-table>thead').first(); | ||||
| @@ -27,15 +30,39 @@ define( | ||||
|             this.conductor = openmct.conductor; | ||||
|             this.toiFormatter = undefined; | ||||
|             this.formatService = formatService; | ||||
|             this.callbacks = {}; | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(MCTTableController.prototype).filter(function (key) { | ||||
|                 return typeof MCTTableController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = MCTTableController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|             _.bindAll(this, [ | ||||
|                 'addRows', | ||||
|                 'binarySearch', | ||||
|                 'buildLargestRow', | ||||
|                 'changeBounds', | ||||
|                 'changeTimeOfInterest', | ||||
|                 'changeTimeSystem', | ||||
|                 'destroyConductorListeners', | ||||
|                 'digest', | ||||
|                 'filterAndSort', | ||||
|                 'filterRows', | ||||
|                 'firstVisible', | ||||
|                 'insertSorted', | ||||
|                 'lastVisible', | ||||
|                 'onRowClick', | ||||
|                 'onScroll', | ||||
|                 'removeRows', | ||||
|                 'resize', | ||||
|                 'scrollToBottom', | ||||
|                 'scrollToRow', | ||||
|                 'setElementSizes', | ||||
|                 'setHeaders', | ||||
|                 'setRows', | ||||
|                 'setTimeOfInterestRow', | ||||
|                 'setVisibleRows', | ||||
|                 'sortComparator', | ||||
|                 'sortRows' | ||||
|             ]); | ||||
|  | ||||
|             this.scrollable.on('scroll', this.onScroll.bind(this)); | ||||
|             this.scrollable.on('scroll', this.onScroll); | ||||
|  | ||||
|             $scope.visibleRows = []; | ||||
|  | ||||
| @@ -86,7 +113,7 @@ define( | ||||
|                     $scope.sortDirection = 'asc'; | ||||
|                 } | ||||
|                 self.setRows($scope.rows); | ||||
|                 self.setTimeOfInterest(self.conductor.timeOfInterest()); | ||||
|                 self.setTimeOfInterestRow(self.conductor.timeOfInterest()); | ||||
|             }; | ||||
|  | ||||
|             /* | ||||
| @@ -95,20 +122,28 @@ define( | ||||
|             $scope.$watchCollection('filters', function () { | ||||
|                 self.setRows($scope.rows); | ||||
|             }); | ||||
|             $scope.$watch('headers', this.setHeaders); | ||||
|             $scope.$watch('headers', function (newHeaders, oldHeaders) { | ||||
|                 if (newHeaders !== oldHeaders) { | ||||
|                     this.setHeaders(newHeaders); | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|             $scope.$watch('rows', this.setRows); | ||||
|  | ||||
|             /* | ||||
|              * Listen for rows added individually (eg. for real-time tables) | ||||
|              */ | ||||
|             $scope.$on('add:row', this.addRow); | ||||
|             $scope.$on('remove:row', this.removeRow); | ||||
|             $scope.$on('add:rows', this.addRows); | ||||
|             $scope.$on('remove:rows', this.removeRows); | ||||
|  | ||||
|             /** | ||||
|              * Populated from the default-sort attribute on MctTable | ||||
|              * directive tag. | ||||
|              */ | ||||
|             $scope.$watch('sortColumn', $scope.toggleSort); | ||||
|             $scope.$watch('defaultSort', function (newColumn, oldColumn) { | ||||
|                 if (newColumn !== oldColumn) { | ||||
|                     $scope.toggleSort(newColumn); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             /* | ||||
|              * Listen for resize events to trigger recalculation of table width | ||||
| @@ -125,7 +160,7 @@ define( | ||||
|                     this.destroyConductorListeners(); | ||||
|  | ||||
|                     this.conductor.on('timeSystem', this.changeTimeSystem); | ||||
|                     this.conductor.on('timeOfInterest', this.setTimeOfInterest); | ||||
|                     this.conductor.on('timeOfInterest', this.changeTimeOfInterest); | ||||
|                     this.conductor.on('bounds', this.changeBounds); | ||||
|  | ||||
|                     // If time system defined, set initially | ||||
| @@ -135,12 +170,20 @@ define( | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|  | ||||
|             $scope.$on('$destroy', this.destroyConductorListeners); | ||||
|             $scope.$on('$destroy', function () { | ||||
|                 this.scrollable.off('scroll', this.onScroll); | ||||
|                 this.destroyConductorListeners(); | ||||
|  | ||||
|                 // In case for some reason this controller instance lingers around, | ||||
|                 // destroy scope as it can be extremely large for large tables. | ||||
|                 delete this.$scope; | ||||
|  | ||||
|             }.bind(this)); | ||||
|         } | ||||
|  | ||||
|         MCTTableController.prototype.destroyConductorListeners = function () { | ||||
|             this.conductor.off('timeSystem', this.changeTimeSystem); | ||||
|             this.conductor.off('timeOfInterest', this.setTimeOfInterest); | ||||
|             this.conductor.off('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.conductor.off('bounds', this.changeBounds); | ||||
|         }; | ||||
|  | ||||
| @@ -155,15 +198,7 @@ define( | ||||
|          * @private | ||||
|          */ | ||||
|         MCTTableController.prototype.scrollToBottom = function () { | ||||
|             var self = this; | ||||
|  | ||||
|             //Use timeout to defer execution until next digest when any | ||||
|             // pending UI changes have completed, eg. a new row in the table. | ||||
|             if (this.$scope.autoScroll) { | ||||
|                 this.$timeout(function () { | ||||
|                     self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight; | ||||
|                 }); | ||||
|             } | ||||
|             this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -171,18 +206,24 @@ define( | ||||
|          * `add:row` broadcast event. | ||||
|          * @private | ||||
|          */ | ||||
|         MCTTableController.prototype.addRow = function (event, rowIndex) { | ||||
|             var row = this.$scope.rows[rowIndex]; | ||||
|  | ||||
|         MCTTableController.prototype.addRows = function (event, rows) { | ||||
|             //Does the row pass the current filter? | ||||
|             if (this.filterRows([row]).length === 1) { | ||||
|                 //Insert the row into the correct position in the array | ||||
|                 this.insertSorted(this.$scope.displayRows, row); | ||||
|             if (this.filterRows(rows).length > 0) { | ||||
|                 rows.forEach(this.insertSorted.bind(this, this.$scope.displayRows)); | ||||
|  | ||||
|                 //Resize the columns , then update the rows visible in the table | ||||
|                 this.resize([this.$scope.sizingRow, row]) | ||||
|                     .then(this.setVisibleRows.bind(this)) | ||||
|                     .then(this.scrollToBottom.bind(this)); | ||||
|                 this.resize([this.$scope.sizingRow].concat(rows)) | ||||
|                     .then(this.setVisibleRows) | ||||
|                     .then(function () { | ||||
|                         if (this.$scope.autoScroll) { | ||||
|                             this.scrollToBottom(); | ||||
|                         } | ||||
|                     }.bind(this)); | ||||
|  | ||||
|                 var toi = this.conductor.timeOfInterest(); | ||||
|                 if (toi !== -1) { | ||||
|                     this.setTimeOfInterestRow(toi); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -191,31 +232,47 @@ define( | ||||
|          * `remove:row` broadcast event. | ||||
|          * @private | ||||
|          */ | ||||
|         MCTTableController.prototype.removeRow = function (event, rowIndex) { | ||||
|             var row = this.$scope.rows[rowIndex], | ||||
|         MCTTableController.prototype.removeRows = function (event, rows) { | ||||
|             var indexInDisplayRows; | ||||
|             rows.forEach(function (row) { | ||||
|                 // Do a sequential search here. Only way of finding row is by | ||||
|                 // object equality, so array is in effect unsorted. | ||||
|                 indexInDisplayRows = this.$scope.displayRows.indexOf(row); | ||||
|             if (indexInDisplayRows !== -1) { | ||||
|                 this.$scope.displayRows.splice(indexInDisplayRows, 1); | ||||
|                 this.setVisibleRows(); | ||||
|             } | ||||
|                 if (indexInDisplayRows !== -1) { | ||||
|                     this.$scope.displayRows.splice(indexInDisplayRows, 1); | ||||
|                 } | ||||
|             }, this); | ||||
|  | ||||
|             this.$scope.sizingRow = this.buildLargestRow([this.$scope.sizingRow].concat(rows)); | ||||
|  | ||||
|             this.setElementSizes(); | ||||
|             this.setVisibleRows() | ||||
|                 .then(function () { | ||||
|                     if (this.$scope.autoScroll) { | ||||
|                         this.scrollToBottom(); | ||||
|                     } | ||||
|                 }.bind(this)); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         MCTTableController.prototype.onScroll = function (event) { | ||||
|             //If user scrolls away from bottom, disable auto-scroll. | ||||
|             // Auto-scroll will be re-enabled if user scrolls to bottom again. | ||||
|             if (this.scrollable[0].scrollTop < | ||||
|                 (this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight)) { | ||||
|                 this.$scope.autoScroll = false; | ||||
|             } else { | ||||
|                 this.$scope.autoScroll = true; | ||||
|             } | ||||
|             this.setVisibleRows(); | ||||
|             this.$scope.$digest(); | ||||
|             this.$window.requestAnimationFrame(function () { | ||||
|                 this.setVisibleRows(); | ||||
|                 this.digest(); | ||||
|  | ||||
|                 // If user scrolls away from bottom, disable auto-scroll. | ||||
|                 // Auto-scroll will be re-enabled if user scrolls to bottom again. | ||||
|                 if (this.scrollable[0].scrollTop < | ||||
|                     (this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight) - 20) { | ||||
|                     this.$scope.autoScroll = false; | ||||
|                 } else { | ||||
|                     this.$scope.autoScroll = true; | ||||
|                 } | ||||
|                 this.scrolling = false; | ||||
|             }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -293,8 +350,7 @@ define( | ||||
|                     this.$scope.visibleRows[0].rowIndex === start && | ||||
|                     this.$scope.visibleRows[this.$scope.visibleRows.length - 1] | ||||
|                         .rowIndex === end) { | ||||
|  | ||||
|                     return; // don't update if no changes are required. | ||||
|                     return this.digest(); | ||||
|                 } | ||||
|             } | ||||
|             //Set visible rows from display rows, based on calculated offset. | ||||
| @@ -307,6 +363,7 @@ define( | ||||
|                         contents: row | ||||
|                     }; | ||||
|                 }); | ||||
|             return this.digest(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -522,6 +579,28 @@ define( | ||||
|             return largestRow; | ||||
|         }; | ||||
|  | ||||
|         // Will effectively cap digests at 60Hz | ||||
|         // Also turns digest into a promise allowing code to force digest, then | ||||
|         // schedule something to happen afterwards | ||||
|         MCTTableController.prototype.digest = function () { | ||||
|             var scope = this.$scope; | ||||
|             var self = this; | ||||
|             var raf = this.$window.requestAnimationFrame; | ||||
|             var promise = this.digestPromise; | ||||
|  | ||||
|             if (!promise) { | ||||
|                 self.digestPromise = promise = new Promise(function (resolve) { | ||||
|                     raf(function () { | ||||
|                         scope.$digest(); | ||||
|                         self.digestPromise = undefined; | ||||
|                         resolve(); | ||||
|                     }); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             return promise; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Calculates the widest row in the table, and if necessary, resizes | ||||
|          * the table accordingly | ||||
| @@ -533,7 +612,7 @@ define( | ||||
|          */ | ||||
|         MCTTableController.prototype.resize = function (rows) { | ||||
|             this.$scope.sizingRow = this.buildLargestRow(rows); | ||||
|             return this.$timeout(this.setElementSizes.bind(this)); | ||||
|             return this.digest().then(this.setElementSizes); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -562,19 +641,20 @@ define( | ||||
|             } | ||||
|  | ||||
|             this.$scope.displayRows = this.filterAndSort(newRows || []); | ||||
|             this.resize(newRows) | ||||
|                 .then(this.setVisibleRows) | ||||
|             return this.resize(newRows) | ||||
|                 .then(function (rows) { | ||||
|                     return this.setVisibleRows(rows); | ||||
|                 }.bind(this)) | ||||
|                 //Timeout following setVisibleRows to allow digest to | ||||
|                 // perform DOM changes, otherwise scrollTo won't work. | ||||
|                 .then(this.$timeout) | ||||
|                 .then(function () { | ||||
|                     //If TOI specified, scroll to it | ||||
|                     var timeOfInterest = this.conductor.timeOfInterest(); | ||||
|                     if (timeOfInterest) { | ||||
|                         this.setTimeOfInterest(timeOfInterest); | ||||
|                         this.setTimeOfInterestRow(timeOfInterest); | ||||
|                         this.scrollToRow(this.$scope.toiRowIndex); | ||||
|                     } | ||||
|                 }.bind(this)); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -615,6 +695,7 @@ define( | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Scroll the view to a given row index | ||||
|          * @param displayRowIndex {number} The index in the displayed rows | ||||
|          * to scroll to. | ||||
|          */ | ||||
| @@ -635,7 +716,7 @@ define( | ||||
|          * Update rows with new data.  If filtering is enabled, rows | ||||
|          * will be sorted before display. | ||||
|          */ | ||||
|         MCTTableController.prototype.setTimeOfInterest = function (newTOI) { | ||||
|         MCTTableController.prototype.setTimeOfInterestRow = function (newTOI) { | ||||
|             var isSortedByTime = | ||||
|                 this.$scope.timeColumns && | ||||
|                 this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1; | ||||
| @@ -652,17 +733,24 @@ define( | ||||
|  | ||||
|                 if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) { | ||||
|                     this.$scope.toiRowIndex = rowIndex; | ||||
|                     this.scrollToRow(this.$scope.toiRowIndex); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         MCTTableController.prototype.changeTimeOfInterest = function (newTOI) { | ||||
|             this.setTimeOfInterestRow(newTOI); | ||||
|             this.scrollToRow(this.$scope.toiRowIndex); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * On zoom, pan, etc. reset TOI | ||||
|          * @param bounds | ||||
|          */ | ||||
|         MCTTableController.prototype.changeBounds = function (bounds) { | ||||
|             this.setTimeOfInterest(this.conductor.timeOfInterest()); | ||||
|             this.setTimeOfInterestRow(this.conductor.timeOfInterest()); | ||||
|             if (this.$scope.toiRowIndex !== -1) { | ||||
|                 this.scrollToRow(this.$scope.toiRowIndex); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -1,76 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         './TelemetryTableController' | ||||
|     ], | ||||
|     function (TableController) { | ||||
|  | ||||
|         /** | ||||
|          * Extends TelemetryTableController and adds real-time streaming | ||||
|          * support. | ||||
|          * @memberof platform/features/table | ||||
|          * @param $scope | ||||
|          * @param telemetryHandler | ||||
|          * @param telemetryFormatter | ||||
|          * @constructor | ||||
|          */ | ||||
|         function RealtimeTableController($scope, telemetryHandler, telemetryFormatter, openmct) { | ||||
|             TableController.call(this, $scope, telemetryHandler, telemetryFormatter, openmct); | ||||
|  | ||||
|             this.maxRows = 100000; | ||||
|         } | ||||
|  | ||||
|         RealtimeTableController.prototype = Object.create(TableController.prototype); | ||||
|  | ||||
|         /** | ||||
|          * Overrides method on TelemetryTableController providing handling | ||||
|          * for realtime data. | ||||
|          */ | ||||
|         RealtimeTableController.prototype.addRealtimeData = function () { | ||||
|             var self = this, | ||||
|                 datum, | ||||
|                 row; | ||||
|             this.handle.getTelemetryObjects().forEach(function (telemetryObject) { | ||||
|                 datum = self.handle.getDatum(telemetryObject); | ||||
|                 if (datum) { | ||||
|                     //Populate row values from telemetry datum | ||||
|                     row = self.table.getRowValues(telemetryObject, datum); | ||||
|                     self.$scope.rows.push(row); | ||||
|  | ||||
|                     //Inform table that a new row has been added | ||||
|                     if (self.$scope.rows.length > self.maxRows) { | ||||
|                         self.$scope.$broadcast('remove:row', 0); | ||||
|                         self.$scope.rows.shift(); | ||||
|                     } | ||||
|  | ||||
|                     self.$scope.$broadcast('add:row', | ||||
|                         self.$scope.rows.length - 1); | ||||
|                 } | ||||
|             }); | ||||
|             this.$scope.loading = false; | ||||
|         }; | ||||
|  | ||||
|         return RealtimeTableController; | ||||
|     } | ||||
| ); | ||||
| @@ -19,6 +19,7 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /* global console*/ | ||||
|  | ||||
| /** | ||||
|  * This bundle adds a table view for displaying telemetry data. | ||||
| @@ -26,9 +27,13 @@ | ||||
|  */ | ||||
| define( | ||||
|     [ | ||||
|         '../TableConfiguration' | ||||
|         '../TableConfiguration', | ||||
|         '../../../../../src/api/objects/object-utils', | ||||
|         '../TelemetryCollection', | ||||
|         'lodash' | ||||
|  | ||||
|     ], | ||||
|     function (TableConfiguration) { | ||||
|     function (TableConfiguration, objectUtils, TelemetryCollection, _) { | ||||
|  | ||||
|         /** | ||||
|          * The TableController is responsible for getting data onto the page | ||||
| @@ -36,183 +41,412 @@ define( | ||||
|          * configuration, and telemetry subscriptions. | ||||
|          * @memberof platform/features/table | ||||
|          * @param $scope | ||||
|          * @param telemetryHandler | ||||
|          * @param telemetryFormatter | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TelemetryTableController( | ||||
|             $scope, | ||||
|             telemetryHandler, | ||||
|             telemetryFormatter, | ||||
|             $timeout, | ||||
|             openmct | ||||
|         ) { | ||||
|             var self = this; | ||||
|  | ||||
|             this.$scope = $scope; | ||||
|             this.$timeout = $timeout; | ||||
|             this.openmct = openmct; | ||||
|             this.batchSize = 1000; | ||||
|  | ||||
|             /* | ||||
|              * Initialization block | ||||
|              */ | ||||
|             this.columns = {}; //Range and Domain columns | ||||
|             this.handle = undefined; | ||||
|             this.telemetryHandler = telemetryHandler; | ||||
|             this.table = new TableConfiguration($scope.domainObject, | ||||
|                 telemetryFormatter); | ||||
|             this.changeListeners = []; | ||||
|             this.conductor = openmct.conductor; | ||||
|  | ||||
|             $scope.rows = []; | ||||
|  | ||||
|             // Subscribe to telemetry when a domain object becomes available | ||||
|             this.$scope.$watch('domainObject', function () { | ||||
|                 self.subscribe(); | ||||
|                 self.registerChangeListeners(); | ||||
|             }); | ||||
|  | ||||
|             this.destroy = this.destroy.bind(this); | ||||
|  | ||||
|             // Unsubscribe when the plot is destroyed | ||||
|             this.$scope.$on("$destroy", this.destroy); | ||||
|             this.unobserveObject = undefined; | ||||
|             this.subscriptions = []; | ||||
|             this.timeColumns = []; | ||||
|             $scope.rows = []; | ||||
|             this.table = new TableConfiguration($scope.domainObject, | ||||
|                 openmct); | ||||
|             this.lastBounds = this.openmct.conductor.bounds(); | ||||
|             this.lastRequestTime = 0; | ||||
|             this.telemetry = new TelemetryCollection(); | ||||
|  | ||||
|             /* | ||||
|              * Create a new format object from legacy object, and replace it | ||||
|              * when it changes | ||||
|              */ | ||||
|             this.newObject = objectUtils.toNewFormat($scope.domainObject.getModel(), | ||||
|                 $scope.domainObject.getId()); | ||||
|  | ||||
|             this.sortByTimeSystem = this.sortByTimeSystem.bind(this); | ||||
|             this.conductor.on('timeSystem', this.sortByTimeSystem); | ||||
|             this.conductor.off('timeSystem', this.sortByTimeSystem); | ||||
|             _.bindAll(this, [ | ||||
|                 'destroy', | ||||
|                 'sortByTimeSystem', | ||||
|                 'loadColumns', | ||||
|                 'getHistoricalData', | ||||
|                 'subscribeToNewData', | ||||
|                 'changeBounds', | ||||
|                 'setScroll', | ||||
|                 'addRowsToTable', | ||||
|                 'removeRowsFromTable' | ||||
|             ]); | ||||
|  | ||||
|             // Retrieve data when domain object is available. | ||||
|             // Also deferring telemetry request makes testing easier as controller | ||||
|             // construction has no unintended consequences. | ||||
|             $scope.$watch("domainObject", function () { | ||||
|                 this.getData(); | ||||
|                 this.registerChangeListeners(); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.setScroll(this.openmct.conductor.follow()); | ||||
|  | ||||
|             this.$scope.$on("$destroy", this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          * @param {boolean} scroll | ||||
|          */ | ||||
|         TelemetryTableController.prototype.setScroll = function (scroll) { | ||||
|             this.$scope.autoScroll = scroll; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Based on the selected time system, find a matching domain column | ||||
|          * to sort by. By default will just match on key. | ||||
|          * @param timeSystem | ||||
|          * | ||||
|          * @private | ||||
|          * @param {TimeSystem} timeSystem | ||||
|          */ | ||||
|         TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) { | ||||
|             var scope = this.$scope; | ||||
|             var sortColumn; | ||||
|             scope.defaultSort = undefined; | ||||
|  | ||||
|             if (timeSystem) { | ||||
|                 this.table.columns.forEach(function (column) { | ||||
|                     if (column.domainMetadata && column.domainMetadata.key === timeSystem.metadata.key) { | ||||
|                         scope.defaultSort = column.getTitle(); | ||||
|                     if (column.getKey() === timeSystem.metadata.key) { | ||||
|                         sortColumn = column; | ||||
|                     } | ||||
|                 }); | ||||
|                 if (sortColumn) { | ||||
|                     scope.defaultSort = sortColumn.getTitle(); | ||||
|                     this.telemetry.sort(sortColumn.getTitle() + '.value'); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         TelemetryTableController.prototype.unregisterChangeListeners = function () { | ||||
|             this.changeListeners.forEach(function (listener) { | ||||
|                 return listener && listener(); | ||||
|             }); | ||||
|             this.changeListeners = []; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Defer registration of change listeners until domain object is | ||||
|          * available in order to avoid race conditions | ||||
|          * Attaches listeners that respond to state change in domain object, | ||||
|          * conductor, and receipt of telemetry | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         TelemetryTableController.prototype.registerChangeListeners = function () { | ||||
|             var self = this; | ||||
|             this.unregisterChangeListeners(); | ||||
|  | ||||
|             // When composition changes, re-subscribe to the various | ||||
|             // telemetry subscriptions | ||||
|             this.changeListeners.push(this.$scope.$watchCollection( | ||||
|                 'domainObject.getModel().composition', | ||||
|                 function (newVal, oldVal) { | ||||
|                     if (newVal !== oldVal) { | ||||
|                         self.subscribe(); | ||||
|                     } | ||||
|                 }) | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Release the current subscription (called when scope is destroyed) | ||||
|          */ | ||||
|         TelemetryTableController.prototype.destroy = function () { | ||||
|             if (this.handle) { | ||||
|                 this.handle.unsubscribe(); | ||||
|                 this.handle = undefined; | ||||
|             if (this.unobserveObject) { | ||||
|                 this.unobserveObject(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Function for handling realtime data when it is available. This | ||||
|          * will be called by the telemetry framework when new data is | ||||
|          * available. | ||||
|          * | ||||
|          * Method should be overridden by specializing class. | ||||
|          */ | ||||
|         TelemetryTableController.prototype.addRealtimeData = function () { | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Function for handling historical data. Will be called by | ||||
|          * telemetry framework when requested historical data is available. | ||||
|          * Should be overridden by specializing class. | ||||
|          */ | ||||
|         TelemetryTableController.prototype.addHistoricalData = function () { | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          Create a new subscription. This can be overridden by children to | ||||
|          change default behaviour (which is to retrieve historical telemetry | ||||
|          only). | ||||
|          */ | ||||
|         TelemetryTableController.prototype.subscribe = function () { | ||||
|             if (this.handle) { | ||||
|                 this.handle.unsubscribe(); | ||||
|             } | ||||
|             this.$scope.loading = true; | ||||
|  | ||||
|             this.handle = this.$scope.domainObject && this.telemetryHandler.handle( | ||||
|                     this.$scope.domainObject, | ||||
|                     this.addRealtimeData.bind(this), | ||||
|                     true // Lossless | ||||
|             this.unobserveObject = this.openmct.objects.observe(this.newObject, "*", | ||||
|                     function (domainObject) { | ||||
|                         this.newObject = domainObject; | ||||
|                         this.getData(); | ||||
|                     }.bind(this) | ||||
|                 ); | ||||
|  | ||||
|             this.handle.request({}).then(this.addHistoricalData.bind(this)); | ||||
|             this.openmct.conductor.on('timeSystem', this.sortByTimeSystem); | ||||
|             this.openmct.conductor.on('bounds', this.changeBounds); | ||||
|             this.openmct.conductor.on('follow', this.setScroll); | ||||
|  | ||||
|             this.setup(); | ||||
|         }; | ||||
|  | ||||
|         TelemetryTableController.prototype.populateColumns = function (telemetryMetadata) { | ||||
|             this.table.populateColumns(telemetryMetadata); | ||||
|  | ||||
|             //Identify time columns | ||||
|             telemetryMetadata.forEach(function (metadatum) { | ||||
|                 //Push domains first | ||||
|                 (metadatum.domains || []).forEach(function (domainMetadata) { | ||||
|                     this.timeColumns.push(domainMetadata.name); | ||||
|                 }.bind(this)); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             var timeSystem = this.conductor.timeSystem(); | ||||
|             if (timeSystem) { | ||||
|                 this.sortByTimeSystem(timeSystem); | ||||
|             } | ||||
|             this.telemetry.on('added', this.addRowsToTable); | ||||
|             this.telemetry.on('discarded', this.removeRowsFromTable); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Setup table columns based on domain object metadata | ||||
|          * On receipt of new telemetry, informs mct-table directive that new rows | ||||
|          * are available and passes populated rows to it | ||||
|          * | ||||
|          * @private | ||||
|          * @param rows | ||||
|          */ | ||||
|         TelemetryTableController.prototype.setup = function () { | ||||
|             var handle = this.handle, | ||||
|                 self = this; | ||||
|         TelemetryTableController.prototype.addRowsToTable = function (rows) { | ||||
|             this.$scope.$broadcast('add:rows', rows); | ||||
|         }; | ||||
|  | ||||
|             if (handle) { | ||||
|                 this.timeColumns = []; | ||||
|                 handle.promiseTelemetryObjects().then(function () { | ||||
|                     self.$scope.headers = []; | ||||
|                     self.$scope.rows = []; | ||||
|         /** | ||||
|          * When rows are to be removed, informs mct-table directive. Row removal | ||||
|          * happens when rows call outside the bounds of the time conductor | ||||
|          * | ||||
|          * @private | ||||
|          * @param rows | ||||
|          */ | ||||
|         TelemetryTableController.prototype.removeRowsFromTable = function (rows) { | ||||
|             this.$scope.$broadcast('remove:rows', rows); | ||||
|         }; | ||||
|  | ||||
|                     self.populateColumns(handle.getMetadata()); | ||||
|                     self.filterColumns(); | ||||
|         /** | ||||
|          * On Time Conductor bounds change, update displayed telemetry. In the | ||||
|          * case of a tick, previously visible telemetry that is now out of band | ||||
|          * will be removed from the table. | ||||
|          * @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds | ||||
|          */ | ||||
|         TelemetryTableController.prototype.changeBounds = function (bounds) { | ||||
|             var follow = this.openmct.conductor.follow(); | ||||
|             var isTick = follow && | ||||
|                 bounds.start !== this.lastBounds.start && | ||||
|                 bounds.end !== this.lastBounds.end; | ||||
|  | ||||
|                     // When table column configuration changes, (due to being | ||||
|                     // selected or deselected), filter columns appropriately. | ||||
|                     self.changeListeners.push(self.$scope.$watchCollection( | ||||
|                         'domainObject.getModel().configuration.table.columns', | ||||
|                         self.filterColumns.bind(self) | ||||
|                     )); | ||||
|                 }); | ||||
|             if (isTick) { | ||||
|                 this.telemetry.bounds(bounds); | ||||
|             } else { | ||||
|                 // Is fixed bounds change | ||||
|                 this.getData(); | ||||
|             } | ||||
|             this.lastBounds = bounds; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Clean controller, deregistering listeners etc. | ||||
|          */ | ||||
|         TelemetryTableController.prototype.destroy = function () { | ||||
|  | ||||
|             this.openmct.conductor.off('timeSystem', this.sortByTimeSystem); | ||||
|             this.openmct.conductor.off('bounds', this.changeBounds); | ||||
|             this.openmct.conductor.off('follow', this.setScroll); | ||||
|  | ||||
|             this.subscriptions.forEach(function (subscription) { | ||||
|                 subscription(); | ||||
|             }); | ||||
|  | ||||
|             if (this.unobserveObject) { | ||||
|                 this.unobserveObject(); | ||||
|             } | ||||
|             this.subscriptions = []; | ||||
|  | ||||
|             if (this.timeoutHandle) { | ||||
|                 this.$timeout.cancel(this.timeoutHandle); | ||||
|             } | ||||
|  | ||||
|             // In case controller instance lingers around (currently there is a | ||||
|             // temporary memory leak with PlotController), clean up scope as it | ||||
|             // can be extremely large. | ||||
|             this.$scope = null; | ||||
|             this.table = null; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * For given objects, populate column metadata and table headers. | ||||
|          * @private | ||||
|          * @param {module:openmct.DomainObject[]} objects the domain objects for | ||||
|          * which columns should be populated | ||||
|          */ | ||||
|         TelemetryTableController.prototype.loadColumns = function (objects) { | ||||
|             var telemetryApi = this.openmct.telemetry; | ||||
|  | ||||
|             this.$scope.headers = []; | ||||
|  | ||||
|             if (objects.length > 0) { | ||||
|                 var metadatas = objects.map(telemetryApi.getMetadata.bind(telemetryApi)); | ||||
|                 var allColumns = telemetryApi.commonValuesForHints(metadatas, []); | ||||
|  | ||||
|                 this.table.populateColumns(allColumns); | ||||
|  | ||||
|                 var domainColumns = telemetryApi.commonValuesForHints(metadatas, ['x']); | ||||
|                 this.timeColumns = domainColumns.map(function (metadatum) { | ||||
|                     return metadatum.name; | ||||
|                 }); | ||||
|  | ||||
|                 this.filterColumns(); | ||||
|  | ||||
|                 // Default to no sort on underlying telemetry collection. Sorting | ||||
|                 // is necessary to do bounds filtering, but this is only possible | ||||
|                 // if data matches selected time system | ||||
|                 this.telemetry.sort(undefined); | ||||
|  | ||||
|                 var timeSystem = this.openmct.conductor.timeSystem(); | ||||
|                 if (timeSystem) { | ||||
|                     this.sortByTimeSystem(timeSystem); | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|             return objects; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Request telemetry data from an historical store for given objects. | ||||
|          * @private | ||||
|          * @param {object[]} The domain objects to request telemetry for | ||||
|          * @returns {Promise} resolved when historical data is available | ||||
|          */ | ||||
|         TelemetryTableController.prototype.getHistoricalData = function (objects) { | ||||
|             var self = this; | ||||
|             var openmct = this.openmct; | ||||
|             var bounds = openmct.conductor.bounds(); | ||||
|             var scope = this.$scope; | ||||
|             var rowData = []; | ||||
|             var processedObjects = 0; | ||||
|             var requestTime = this.lastRequestTime = Date.now(); | ||||
|             var telemetryCollection = this.telemetry; | ||||
|  | ||||
|             var promise = new Promise(function (resolve, reject) { | ||||
|                 /* | ||||
|                  * On completion of batched processing, set the rows on scope | ||||
|                  */ | ||||
|                 function finishProcessing() { | ||||
|                     telemetryCollection.add(rowData); | ||||
|                     scope.rows = telemetryCollection.telemetry; | ||||
|                     scope.loading = false; | ||||
|  | ||||
|                     resolve(scope.rows); | ||||
|                 } | ||||
|  | ||||
|                 /* | ||||
|                  * Process a batch of historical data | ||||
|                  */ | ||||
|                 function processData(historicalData, index, limitEvaluator) { | ||||
|                     if (index >= historicalData.length) { | ||||
|                         processedObjects++; | ||||
|  | ||||
|                         if (processedObjects === objects.length) { | ||||
|                             finishProcessing(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         rowData = rowData.concat(historicalData.slice(index, index + self.batchSize) | ||||
|                             .map(self.table.getRowValues.bind(self.table, limitEvaluator))); | ||||
|  | ||||
|                         /* | ||||
|                          Use timeout to yield process to other UI activities. On | ||||
|                          return, process next batch | ||||
|                          */ | ||||
|                         self.timeoutHandle = self.$timeout(function () { | ||||
|                             processData(historicalData, index + self.batchSize, limitEvaluator); | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 function makeTableRows(object, historicalData) { | ||||
|                     // Only process the most recent request | ||||
|                     if (requestTime === self.lastRequestTime) { | ||||
|                         var limitEvaluator = openmct.telemetry.limitEvaluator(object); | ||||
|                         processData(historicalData, 0, limitEvaluator); | ||||
|                     } else { | ||||
|                         resolve(rowData); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 /* | ||||
|                 Use the telemetry API to request telemetry for a given object | ||||
|                  */ | ||||
|                 function requestData(object) { | ||||
|                     return openmct.telemetry.request(object, { | ||||
|                         start: bounds.start, | ||||
|                         end: bounds.end | ||||
|                     }).then(makeTableRows.bind(undefined, object)) | ||||
|                         .catch(reject); | ||||
|                 } | ||||
|                 this.$timeout.cancel(this.timeoutHandle); | ||||
|  | ||||
|                 if (objects.length > 0) { | ||||
|                     objects.forEach(requestData); | ||||
|                 } else { | ||||
|                     scope.loading = false; | ||||
|                     resolve([]); | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|  | ||||
|             return promise; | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         /** | ||||
|          * Subscribe to real-time data for the given objects. | ||||
|          * @private | ||||
|          * @param {object[]} objects The objects to subscribe to. | ||||
|          */ | ||||
|         TelemetryTableController.prototype.subscribeToNewData = function (objects) { | ||||
|             var telemetryApi = this.openmct.telemetry; | ||||
|             var telemetryCollection = this.telemetry; | ||||
|             //Set table max length to avoid unbounded growth. | ||||
|             //var maxRows = 100000; | ||||
|             var maxRows = Number.MAX_VALUE; | ||||
|             var limitEvaluator; | ||||
|             var added = false; | ||||
|             var scope = this.$scope; | ||||
|             var table = this.table; | ||||
|  | ||||
|             this.subscriptions.forEach(function (subscription) { | ||||
|                 subscription(); | ||||
|             }); | ||||
|             this.subscriptions = []; | ||||
|  | ||||
|             function newData(domainObject, datum) { | ||||
|                 limitEvaluator = telemetryApi.limitEvaluator(domainObject); | ||||
|                 added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]); | ||||
|  | ||||
|                 //Inform table that a new row has been added | ||||
|                 if (scope.rows.length > maxRows) { | ||||
|                     scope.$broadcast('remove:rows', scope.rows[0]); | ||||
|                     scope.rows.shift(); | ||||
|                 } | ||||
|                 if (!scope.loading && added) { | ||||
|                     scope.$broadcast('add:row', | ||||
|                         scope.rows.length - 1); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             objects.forEach(function (object) { | ||||
|                 this.subscriptions.push( | ||||
|                     telemetryApi.subscribe(object, newData.bind(this, object), {})); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             return objects; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Request historical data, and subscribe to for real-time data. | ||||
|          * @private | ||||
|          * @returns {Promise} A promise that is resolved once subscription is | ||||
|          * established, and historical telemetry is received and processed. | ||||
|          */ | ||||
|         TelemetryTableController.prototype.getData = function () { | ||||
|             var telemetryApi = this.openmct.telemetry; | ||||
|             var compositionApi = this.openmct.composition; | ||||
|             var scope = this.$scope; | ||||
|             var newObject = this.newObject; | ||||
|  | ||||
|             this.telemetry.clear(); | ||||
|             this.telemetry.bounds(this.openmct.conductor.bounds()); | ||||
|  | ||||
|             this.$scope.loading = true; | ||||
|  | ||||
|             function error(e) { | ||||
|                 scope.loading = false; | ||||
|                 console.error(e.stack); | ||||
|             } | ||||
|  | ||||
|             function filterForTelemetry(objects) { | ||||
|                 return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi)); | ||||
|             } | ||||
|  | ||||
|             function getDomainObjects() { | ||||
|                 var objects = [newObject]; | ||||
|                 var composition = compositionApi.get(newObject); | ||||
|  | ||||
|                 if (composition) { | ||||
|                     return composition | ||||
|                         .load() | ||||
|                         .then(function (children) { | ||||
|                             return objects.concat(children); | ||||
|                         }); | ||||
|                 } else { | ||||
|                     return Promise.resolve(objects); | ||||
|                 } | ||||
|             } | ||||
|             scope.rows = []; | ||||
|  | ||||
|             return getDomainObjects() | ||||
|                 .then(filterForTelemetry) | ||||
|                 .then(this.loadColumns) | ||||
|                 .then(this.subscribeToNewData) | ||||
|                 .then(this.getHistoricalData) | ||||
|                 .catch(error); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -77,13 +77,13 @@ define( | ||||
|          * | ||||
|          * @constructor | ||||
|          */ | ||||
|         function MCTTable($timeout) { | ||||
|         function MCTTable() { | ||||
|             return { | ||||
|                 restrict: "E", | ||||
|                 template: TableTemplate, | ||||
|                 controller: [ | ||||
|                     '$scope', | ||||
|                     '$timeout', | ||||
|                     '$window', | ||||
|                     '$element', | ||||
|                     'exportService', | ||||
|                     'formatService', | ||||
| @@ -94,6 +94,7 @@ define( | ||||
|                 scope: { | ||||
|                     headers: "=", | ||||
|                     rows: "=", | ||||
|                     formatCell: "=?", | ||||
|                     enableFilter: "=?", | ||||
|                     enableSort: "=?", | ||||
|                     autoScroll: "=?", | ||||
| @@ -104,7 +105,7 @@ define( | ||||
|                     timeColumns: "=?", | ||||
|                     // Indicate a column to sort on. Allows control of sort | ||||
|                     // via configuration (eg. for default sort column). | ||||
|                     sortColumn: "=?" | ||||
|                     defaultSort: "=?" | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|   | ||||
| @@ -1,80 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * MergeModelsSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../src/DomainColumn"], | ||||
|     function (DomainColumn) { | ||||
|  | ||||
|         var TEST_DOMAIN_VALUE = "some formatted domain value"; | ||||
|  | ||||
|         describe("A domain column", function () { | ||||
|             var mockDatum, | ||||
|                 testMetadata, | ||||
|                 mockFormatter, | ||||
|                 column; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|  | ||||
|                 mockFormatter = jasmine.createSpyObj( | ||||
|                     "formatter", | ||||
|                     ["formatDomainValue", "formatRangeValue"] | ||||
|                 ); | ||||
|                 testMetadata = { | ||||
|                     key: "testKey", | ||||
|                     name: "Test Name", | ||||
|                     format: "Test Format" | ||||
|                 }; | ||||
|                 mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE); | ||||
|  | ||||
|                 column = new DomainColumn(testMetadata, mockFormatter); | ||||
|             }); | ||||
|  | ||||
|             it("reports a column header from domain metadata", function () { | ||||
|                 expect(column.getTitle()).toEqual("Test Name"); | ||||
|             }); | ||||
|  | ||||
|             describe("when given a datum", function () { | ||||
|                 beforeEach(function () { | ||||
|                     mockDatum = { | ||||
|                         testKey: "testKeyValue" | ||||
|                     }; | ||||
|                 }); | ||||
|  | ||||
|                 it("looks up data from the given datum", function () { | ||||
|                     expect(column.getValue(undefined, mockDatum)) | ||||
|                         .toEqual({ text: TEST_DOMAIN_VALUE }); | ||||
|                 }); | ||||
|  | ||||
|                 it("uses formatter to format domain values as requested", function () { | ||||
|                     column.getValue(undefined, mockDatum); | ||||
|                     expect(mockFormatter.formatDomainValue) | ||||
|                         .toHaveBeenCalledWith("testKeyValue", "Test Format"); | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,56 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * MergeModelsSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../src/NameColumn"], | ||||
|     function (NameColumn) { | ||||
|  | ||||
|         describe("A name column", function () { | ||||
|             var mockDomainObject, | ||||
|                 column; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     ["getModel"] | ||||
|                 ); | ||||
|                 mockDomainObject.getModel.andReturn({ | ||||
|                     name: "Test object name" | ||||
|                 }); | ||||
|                 column = new NameColumn(); | ||||
|             }); | ||||
|  | ||||
|             it("reports a column header", function () { | ||||
|                 expect(column.getTitle()).toEqual("Name"); | ||||
|             }); | ||||
|  | ||||
|             it("looks up name from an object's model", function () { | ||||
|                 expect(column.getValue(mockDomainObject).text) | ||||
|                     .toEqual("Test object name"); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,74 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * MergeModelsSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     ["../src/RangeColumn"], | ||||
|     function (RangeColumn) { | ||||
|  | ||||
|         var TEST_RANGE_VALUE = "some formatted range value"; | ||||
|  | ||||
|         describe("A range column", function () { | ||||
|             var testDatum, | ||||
|                 testMetadata, | ||||
|                 mockFormatter, | ||||
|                 mockDomainObject, | ||||
|                 column; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 testDatum = { testKey: 123, otherKey: 456 }; | ||||
|                 mockFormatter = jasmine.createSpyObj( | ||||
|                     "formatter", | ||||
|                     ["formatDomainValue", "formatRangeValue"] | ||||
|                 ); | ||||
|                 testMetadata = { | ||||
|                     key: "testKey", | ||||
|                     name: "Test Name" | ||||
|                 }; | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     ["getModel", "getCapability"] | ||||
|                 ); | ||||
|                 mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE); | ||||
|  | ||||
|                 column = new RangeColumn(testMetadata, mockFormatter); | ||||
|             }); | ||||
|  | ||||
|             it("reports a column header from range metadata", function () { | ||||
|                 expect(column.getTitle()).toEqual("Test Name"); | ||||
|             }); | ||||
|  | ||||
|             it("formats range values as numbers", function () { | ||||
|                 expect(column.getValue(mockDomainObject, testDatum).text) | ||||
|                     .toEqual(TEST_RANGE_VALUE); | ||||
|  | ||||
|                 // Make sure that service interactions were as expected | ||||
|                 expect(mockFormatter.formatRangeValue) | ||||
|                     .toHaveBeenCalledWith(testDatum.testKey); | ||||
|                 expect(mockFormatter.formatDomainValue) | ||||
|                     .not.toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -22,109 +22,91 @@ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "../src/TableConfiguration", | ||||
|         "../src/DomainColumn" | ||||
|         "../src/TableConfiguration" | ||||
|     ], | ||||
|     function (Table, DomainColumn) { | ||||
|     function (Table) { | ||||
|  | ||||
|         describe("A table", function () { | ||||
|             var mockDomainObject, | ||||
|                 mockAPI, | ||||
|                 mockTelemetryAPI, | ||||
|                 mockTelemetryFormatter, | ||||
|                 table, | ||||
|                 mockModel; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockDomainObject = jasmine.createSpyObj('domainObject', | ||||
|                     ['getModel', 'useCapability', 'getCapability'] | ||||
|                     ['getModel', 'useCapability', 'getCapability', 'hasCapability'] | ||||
|                 ); | ||||
|                 mockModel = {}; | ||||
|                 mockDomainObject.getModel.andReturn(mockModel); | ||||
|                 mockDomainObject.getCapability.andCallFake(function (name) { | ||||
|                     return name === 'editor' && { | ||||
|                         isEditContextRoot: function () { | ||||
|                             return true; | ||||
|                         } | ||||
|                     }; | ||||
|                 }); | ||||
|  | ||||
|                 mockTelemetryFormatter = jasmine.createSpyObj('telemetryFormatter', | ||||
|                     [ | ||||
|                         'formatDomainValue', | ||||
|                         'formatRangeValue' | ||||
|                         'format' | ||||
|                     ]); | ||||
|                 mockTelemetryFormatter.formatDomainValue.andCallFake(function (valueIn) { | ||||
|                     return valueIn; | ||||
|                 }); | ||||
|                 mockTelemetryFormatter.formatRangeValue.andCallFake(function (valueIn) { | ||||
|                 mockTelemetryFormatter.format.andCallFake(function (valueIn) { | ||||
|                     return valueIn; | ||||
|                 }); | ||||
|  | ||||
|                 table = new Table(mockDomainObject, mockTelemetryFormatter); | ||||
|             }); | ||||
|                 mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [ | ||||
|                     'getValueFormatter' | ||||
|                 ]); | ||||
|                 mockAPI = { | ||||
|                     telemetry: mockTelemetryAPI | ||||
|                 }; | ||||
|                 mockTelemetryAPI.getValueFormatter.andReturn(mockTelemetryFormatter); | ||||
|  | ||||
|             it("Add column with no index adds new column to the end", function () { | ||||
|                 var firstColumn = {title: 'First Column'}, | ||||
|                     secondColumn = {title: 'Second Column'}, | ||||
|                     thirdColumn = {title: 'Third Column'}; | ||||
|  | ||||
|                 table.addColumn(firstColumn); | ||||
|                 table.addColumn(secondColumn); | ||||
|                 table.addColumn(thirdColumn); | ||||
|  | ||||
|                 expect(table.columns).toBeDefined(); | ||||
|                 expect(table.columns.length).toBe(3); | ||||
|                 expect(table.columns[0]).toBe(firstColumn); | ||||
|                 expect(table.columns[1]).toBe(secondColumn); | ||||
|                 expect(table.columns[2]).toBe(thirdColumn); | ||||
|             }); | ||||
|  | ||||
|             it("Add column with index adds new column at the specified" + | ||||
|                 " position", function () { | ||||
|                 var firstColumn = {title: 'First Column'}, | ||||
|                     secondColumn = {title: 'Second Column'}, | ||||
|                     thirdColumn = {title: 'Third Column'}; | ||||
|  | ||||
|                 table.addColumn(firstColumn); | ||||
|                 table.addColumn(thirdColumn); | ||||
|                 table.addColumn(secondColumn, 1); | ||||
|  | ||||
|                 expect(table.columns).toBeDefined(); | ||||
|                 expect(table.columns.length).toBe(3); | ||||
|                 expect(table.columns[0]).toBe(firstColumn); | ||||
|                 expect(table.columns[1]).toBe(secondColumn); | ||||
|                 expect(table.columns[2]).toBe(thirdColumn); | ||||
|                 table = new Table(mockDomainObject, mockAPI); | ||||
|             }); | ||||
|  | ||||
|             describe("Building columns from telemetry metadata", function () { | ||||
|                 var metadata = [{ | ||||
|                     ranges: [ | ||||
|                         { | ||||
|                             name: 'Range 1', | ||||
|                             key: 'range1' | ||||
|                         }, | ||||
|                         { | ||||
|                             name: 'Range 2', | ||||
|                             key: 'range2' | ||||
|                 var metadata = [ | ||||
|                     { | ||||
|                         name: 'Range 1', | ||||
|                         key: 'range1', | ||||
|                         hints: { | ||||
|                             y: 1 | ||||
|                         } | ||||
|                     ], | ||||
|                     domains: [ | ||||
|                         { | ||||
|                             name: 'Domain 1', | ||||
|                             key: 'domain1', | ||||
|                             format: 'utc' | ||||
|                         }, | ||||
|                         { | ||||
|                             name: 'Domain 2', | ||||
|                             key: 'domain2', | ||||
|                             format: 'utc' | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'Range 2', | ||||
|                         key: 'range2', | ||||
|                         hints: { | ||||
|                             y: 2 | ||||
|                         } | ||||
|                     ] | ||||
|                 }]; | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'Domain 1', | ||||
|                         key: 'domain1', | ||||
|                         format: 'utc', | ||||
|                         hints: { | ||||
|                             x: 1 | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         name: 'Domain 2', | ||||
|                         key: 'domain2', | ||||
|                         format: 'utc', | ||||
|                         hints: { | ||||
|                             x: 2 | ||||
|                         } | ||||
|                     } | ||||
|                 ]; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     table.populateColumns(metadata); | ||||
|                 }); | ||||
|  | ||||
|                 it("populates columns", function () { | ||||
|                     expect(table.columns.length).toBe(5); | ||||
|                 }); | ||||
|  | ||||
|                 it("Build columns populates columns with domains to the left", function () { | ||||
|                     expect(table.columns[1] instanceof DomainColumn).toBeTruthy(); | ||||
|                     expect(table.columns[2] instanceof DomainColumn).toBeTruthy(); | ||||
|                     expect(table.columns[3] instanceof DomainColumn).toBeFalsy(); | ||||
|                     expect(table.columns.length).toBe(4); | ||||
|                 }); | ||||
|  | ||||
|                 it("Produces headers for each column based on title", function () { | ||||
| @@ -133,7 +115,7 @@ define( | ||||
|  | ||||
|                     spyOn(firstColumn, 'getTitle'); | ||||
|                     headers = table.getHeaders(); | ||||
|                     expect(headers.length).toBe(5); | ||||
|                     expect(headers.length).toBe(4); | ||||
|                     expect(firstColumn.getTitle).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
| @@ -170,23 +152,33 @@ define( | ||||
|  | ||||
|                     beforeEach(function () { | ||||
|                         datum = { | ||||
|                             'range1': 'range 1 value', | ||||
|                             'range2': 'range 2 value', | ||||
|                             'range1': 10, | ||||
|                             'range2': 20, | ||||
|                             'domain1': 0, | ||||
|                             'domain2': 1 | ||||
|                         }; | ||||
|                         rowValues = table.getRowValues(mockDomainObject, datum); | ||||
|                         var limitEvaluator = { | ||||
|                             evaluate: function () { | ||||
|                                 return { | ||||
|                                     "cssClass": "alarm-class" | ||||
|                                 }; | ||||
|                             } | ||||
|                         }; | ||||
|                         rowValues = table.getRowValues(limitEvaluator, datum); | ||||
|                     }); | ||||
|  | ||||
|                     it("Returns a value for every column", function () { | ||||
|                         expect(rowValues['Range 1'].text).toBeDefined(); | ||||
|                         expect(rowValues['Range 1'].text).toEqual('range 1' + | ||||
|                             ' value'); | ||||
|                         expect(rowValues['Range 1'].text).toEqual(10); | ||||
|                     }); | ||||
|  | ||||
|                     it("Uses the telemetry formatter to appropriately format" + | ||||
|                     it("Applies appropriate css class if limit violated.", function () { | ||||
|                         expect(rowValues['Range 1'].cssClass).toEqual("alarm-class"); | ||||
|                     }); | ||||
|  | ||||
|                     it("Uses telemetry formatter to appropriately format" + | ||||
|                         " telemetry values", function () { | ||||
|                         expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled(); | ||||
|                         expect(mockTelemetryFormatter.format).toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|   | ||||
							
								
								
									
										191
									
								
								platform/features/table/test/TelemetryCollectionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								platform/features/table/test/TelemetryCollectionSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "../src/TelemetryCollection" | ||||
|     ], | ||||
|     function (TelemetryCollection) { | ||||
|  | ||||
|         describe("A telemetry collection", function () { | ||||
|  | ||||
|             var collection; | ||||
|             var telemetryObjects; | ||||
|             var ms; | ||||
|             var integerTextMap = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", | ||||
|                 "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN"]; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 telemetryObjects = [0,9,2,4,7,8,5,1,3,6].map(function (number) { | ||||
|                     ms = number * 1000; | ||||
|                     return { | ||||
|                         timestamp: ms, | ||||
|                         value: { | ||||
|                             integer: number, | ||||
|                             text: integerTextMap[number] | ||||
|                         } | ||||
|                     }; | ||||
|                 }); | ||||
|                 collection = new TelemetryCollection(); | ||||
|             }); | ||||
|  | ||||
|             it("Sorts inserted telemetry by specified field", | ||||
|                 function () { | ||||
|                     collection.sort('value.integer'); | ||||
|                     collection.add(telemetryObjects); | ||||
|                     expect(collection.telemetry[0].value.integer).toBe(0); | ||||
|                     expect(collection.telemetry[1].value.integer).toBe(1); | ||||
|                     expect(collection.telemetry[2].value.integer).toBe(2); | ||||
|                     expect(collection.telemetry[3].value.integer).toBe(3); | ||||
|  | ||||
|                     collection.sort('value.text'); | ||||
|                     expect(collection.telemetry[0].value.text).toBe("EIGHT"); | ||||
|                     expect(collection.telemetry[1].value.text).toBe("FIVE"); | ||||
|                     expect(collection.telemetry[2].value.text).toBe("FOUR"); | ||||
|                     expect(collection.telemetry[3].value.text).toBe("NINE"); | ||||
|                 } | ||||
|             ); | ||||
|  | ||||
|             describe("on bounds change", function () { | ||||
|                 var discardedCallback; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     discardedCallback = jasmine.createSpy("discarded"); | ||||
|                     collection.on("discarded", discardedCallback); | ||||
|                     collection.sort("timestamp"); | ||||
|                     collection.add(telemetryObjects); | ||||
|                     collection.bounds({start: 5000, end: 8000}); | ||||
|                 }); | ||||
|  | ||||
|  | ||||
|                 it("emits an event indicating that telemetry has " + | ||||
|                     "been discarded", function () { | ||||
|                     expect(discardedCallback).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it("discards telemetry data with a time stamp " + | ||||
|                     "before specified start bound", function () { | ||||
|                     var discarded = discardedCallback.mostRecentCall.args[0]; | ||||
|  | ||||
|                     // Expect 5 because as an optimization, the TelemetryCollection | ||||
|                     // will not consider telemetry values that exceed the upper | ||||
|                     // bounds. Arbitrary bounds changes in which the end bound is | ||||
|                     // decreased is assumed to require a new historical query, and | ||||
|                     // hence re-population of the collection anyway | ||||
|                     expect(discarded.length).toBe(5); | ||||
|                     expect(discarded[0].value.integer).toBe(0); | ||||
|                     expect(discarded[1].value.integer).toBe(1); | ||||
|                     expect(discarded[4].value.integer).toBe(4); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("when adding telemetry to a collection", function () { | ||||
|                 var addedCallback; | ||||
|                 beforeEach(function () { | ||||
|                     collection.sort("timestamp"); | ||||
|                     collection.add(telemetryObjects); | ||||
|                     addedCallback = jasmine.createSpy("added"); | ||||
|                     collection.on("added", addedCallback); | ||||
|                 }); | ||||
|  | ||||
|                 it("emits an event", | ||||
|                     function () { | ||||
|                         var addedObject = { | ||||
|                                 timestamp: 10000, | ||||
|                                 value: { | ||||
|                                     integer: 10, | ||||
|                                     text: integerTextMap[10] | ||||
|                                 } | ||||
|                             }; | ||||
|                         collection.add([addedObject]); | ||||
|                         expect(addedCallback).toHaveBeenCalledWith([addedObject]); | ||||
|                     } | ||||
|                 ); | ||||
|                 it("inserts in the correct order", | ||||
|                     function () { | ||||
|                         var addedObjectA = { | ||||
|                             timestamp: 10000, | ||||
|                             value: { | ||||
|                                 integer: 10, | ||||
|                                 text: integerTextMap[10] | ||||
|                             } | ||||
|                         }; | ||||
|                         var addedObjectB = { | ||||
|                             timestamp: 11000, | ||||
|                             value: { | ||||
|                                 integer: 11, | ||||
|                                 text: integerTextMap[11] | ||||
|                             } | ||||
|                         }; | ||||
|                         collection.add([addedObjectB, addedObjectA]); | ||||
|  | ||||
|                         expect(collection.telemetry[11]).toBe(addedObjectB); | ||||
|                     } | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             describe("buffers telemetry", function () { | ||||
|                 var addedObjectA; | ||||
|                 var addedObjectB; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     collection.sort("timestamp"); | ||||
|                     collection.add(telemetryObjects); | ||||
|  | ||||
|                     addedObjectA = { | ||||
|                         timestamp: 10000, | ||||
|                         value: { | ||||
|                             integer: 10, | ||||
|                             text: integerTextMap[10] | ||||
|                         } | ||||
|                     }; | ||||
|                     addedObjectB = { | ||||
|                         timestamp: 11000, | ||||
|                         value: { | ||||
|                             integer: 11, | ||||
|                             text: integerTextMap[11] | ||||
|                         } | ||||
|                     }; | ||||
|  | ||||
|                     collection.bounds({start: 0, end: 10000}); | ||||
|                     collection.add([addedObjectA, addedObjectB]); | ||||
|                 }); | ||||
|                 it("when it falls outside of bounds", function () { | ||||
|                     expect(collection.highBuffer).toBeDefined(); | ||||
|                     expect(collection.highBuffer.length).toBe(1); | ||||
|                     expect(collection.highBuffer[0]).toBe(addedObjectB); | ||||
|                 }); | ||||
|                 it("and adds it to collection when it falls within bounds", function () { | ||||
|                     expect(collection.telemetry.length).toBe(11); | ||||
|                     collection.bounds({start: 0, end: 11000}); | ||||
|                     expect(collection.telemetry.length).toBe(12); | ||||
|                     expect(collection.telemetry[11]).toBe(addedObjectB); | ||||
|                 }); | ||||
|                 it("and removes it from the buffer when it falls within bounds", function () { | ||||
|                     expect(collection.highBuffer.length).toBe(1); | ||||
|                     collection.bounds({start: 0, end: 11000}); | ||||
|                     expect(collection.highBuffer.length).toBe(0); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,380 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "../../src/controllers/HistoricalTableController" | ||||
|     ], | ||||
|     function (TableController) { | ||||
|  | ||||
|         describe('The Table Controller', function () { | ||||
|             var mockScope, | ||||
|                 mockTelemetryHandler, | ||||
|                 mockTelemetryHandle, | ||||
|                 mockTelemetryFormatter, | ||||
|                 mockDomainObject, | ||||
|                 mockTable, | ||||
|                 mockConfiguration, | ||||
|                 mockAngularTimeout, | ||||
|                 mockTimeoutHandle, | ||||
|                 watches, | ||||
|                 mockConductor, | ||||
|                 controller; | ||||
|  | ||||
|             function promise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return promise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             function getCallback(target, event) { | ||||
|                 return target.calls.filter(function (call) { | ||||
|                     return call.args[0] === event; | ||||
|                 })[0].args[1]; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 watches = {}; | ||||
|                 mockScope = jasmine.createSpyObj('scope', [ | ||||
|                     '$on', | ||||
|                     '$watch', | ||||
|                     '$watchCollection' | ||||
|                 ]); | ||||
|  | ||||
|                 mockScope.$on.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|                 mockScope.$watch.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|                 mockScope.$watchCollection.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|  | ||||
|                 mockTimeoutHandle = jasmine.createSpy("timeoutHandle"); | ||||
|                 mockAngularTimeout = jasmine.createSpy("$timeout"); | ||||
|                 mockAngularTimeout.andReturn(mockTimeoutHandle); | ||||
|                 mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout"); | ||||
|  | ||||
|                 mockConfiguration = { | ||||
|                     'range1': true, | ||||
|                     'range2': true, | ||||
|                     'domain1': true | ||||
|                 }; | ||||
|  | ||||
|                 mockTable = jasmine.createSpyObj('table', | ||||
|                     [ | ||||
|                         'populateColumns', | ||||
|                         'buildColumnConfiguration', | ||||
|                         'getRowValues', | ||||
|                         'saveColumnConfiguration' | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockTable.columns = []; | ||||
|                 mockTable.buildColumnConfiguration.andReturn(mockConfiguration); | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj('domainObject', [ | ||||
|                     'getCapability', | ||||
|                     'useCapability', | ||||
|                     'getModel' | ||||
|                 ]); | ||||
|                 mockDomainObject.getModel.andReturn({}); | ||||
|  | ||||
|                 mockScope.domainObject = mockDomainObject; | ||||
|  | ||||
|                 mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [ | ||||
|                     'request', | ||||
|                     'promiseTelemetryObjects', | ||||
|                     'getTelemetryObjects', | ||||
|                     'getMetadata', | ||||
|                     'getSeries', | ||||
|                     'unsubscribe', | ||||
|                     'makeDatum' | ||||
|                 ]); | ||||
|                 mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined)); | ||||
|                 mockTelemetryHandle.request.andReturn(promise(undefined)); | ||||
|                 mockTelemetryHandle.getTelemetryObjects.andReturn([]); | ||||
|                 mockTelemetryHandle.getMetadata.andReturn([]); | ||||
|  | ||||
|                 mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [ | ||||
|                     'handle' | ||||
|                 ]); | ||||
|                 mockTelemetryHandler.handle.andReturn(mockTelemetryHandle); | ||||
|  | ||||
|                 mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                     "timeSystem", | ||||
|                     "on", | ||||
|                     "off" | ||||
|                 ]); | ||||
|  | ||||
|                 controller = new TableController(mockScope, mockTelemetryHandler, | ||||
|                     mockTelemetryFormatter, mockAngularTimeout, {conductor: mockConductor}); | ||||
|  | ||||
|                 controller.table = mockTable; | ||||
|                 controller.handle = mockTelemetryHandle; | ||||
|             }); | ||||
|  | ||||
|             it('subscribes to telemetry handler for telemetry updates', function () { | ||||
|                 controller.subscribe(); | ||||
|                 expect(mockTelemetryHandler.handle).toHaveBeenCalled(); | ||||
|                 expect(mockTelemetryHandle.request).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('Unsubscribes from telemetry when scope is destroyed', function () { | ||||
|                 controller.handle = mockTelemetryHandle; | ||||
|                 watches.$destroy(); | ||||
|                 expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             describe('makes use of the table', function () { | ||||
|  | ||||
|                 it('to create column definitions from telemetry' + | ||||
|                     ' metadata', function () { | ||||
|                     controller.setup(); | ||||
|                     expect(mockTable.populateColumns).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it('to create column configuration, which is written to the' + | ||||
|                     ' object model', function () { | ||||
|                     controller.setup(); | ||||
|                     expect(mockTable.buildColumnConfiguration).toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it('updates the rows on scope when historical telemetry is received', function () { | ||||
|                 var mockSeries = { | ||||
|                         getPointCount: function () { | ||||
|                             return 5; | ||||
|                         }, | ||||
|                         getDomainValue: function () { | ||||
|                             return 'Domain Value'; | ||||
|                         }, | ||||
|                         getRangeValue: function () { | ||||
|                             return 'Range Value'; | ||||
|                         } | ||||
|                     }, | ||||
|                     mockRow = {'domain': 'Domain Value', 'range': 'Range' + | ||||
|                     ' Value'}; | ||||
|  | ||||
|                 mockTelemetryHandle.makeDatum.andCallFake(function () { | ||||
|                     return mockRow; | ||||
|                 }); | ||||
|                 mockTable.getRowValues.andReturn(mockRow); | ||||
|                 mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]); | ||||
|                 mockTelemetryHandle.getSeries.andReturn(mockSeries); | ||||
|  | ||||
|                 controller.addHistoricalData(mockDomainObject, mockSeries); | ||||
|  | ||||
|                 // Angular timeout is called a minumum of twice, regardless | ||||
|                 // of batch size used. | ||||
|                 expect(mockAngularTimeout).toHaveBeenCalled(); | ||||
|                 mockAngularTimeout.mostRecentCall.args[0](); | ||||
|                 expect(mockAngularTimeout.calls.length).toEqual(2); | ||||
|                 mockAngularTimeout.mostRecentCall.args[0](); | ||||
|  | ||||
|                 expect(controller.$scope.rows.length).toBe(5); | ||||
|                 expect(controller.$scope.rows[0]).toBe(mockRow); | ||||
|             }); | ||||
|  | ||||
|             it('filters the visible columns based on configuration', function () { | ||||
|                 controller.filterColumns(); | ||||
|                 expect(controller.$scope.headers.length).toBe(3); | ||||
|                 expect(controller.$scope.headers[2]).toEqual('domain1'); | ||||
|  | ||||
|                 mockConfiguration.domain1 = false; | ||||
|                 controller.filterColumns(); | ||||
|                 expect(controller.$scope.headers.length).toBe(2); | ||||
|                 expect(controller.$scope.headers[2]).toBeUndefined(); | ||||
|             }); | ||||
|  | ||||
|             describe('creates event listeners', function () { | ||||
|                 beforeEach(function () { | ||||
|                     spyOn(controller, 'subscribe'); | ||||
|                     spyOn(controller, 'filterColumns'); | ||||
|                 }); | ||||
|  | ||||
|                 it('triggers telemetry subscription update when domain' + | ||||
|                     ' object changes', function () { | ||||
|                     controller.registerChangeListeners(); | ||||
|                     //'watches' object is populated by fake scope watch and | ||||
|                     // watchCollection functions defined above | ||||
|                     expect(watches.domainObject).toBeDefined(); | ||||
|                     watches.domainObject(mockDomainObject); | ||||
|                     expect(controller.subscribe).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it('triggers telemetry subscription update when domain' + | ||||
|                     ' object composition changes', function () { | ||||
|                     controller.registerChangeListeners(); | ||||
|                     expect(watches['domainObject.getModel().composition']).toBeDefined(); | ||||
|                     watches['domainObject.getModel().composition']([], []); | ||||
|                     expect(controller.subscribe).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it('triggers telemetry subscription update when time' + | ||||
|                     ' conductor bounds change', function () { | ||||
|                     controller.registerChangeListeners(); | ||||
|                     expect(watches['telemetry:display:bounds']).toBeDefined(); | ||||
|                     watches['telemetry:display:bounds'](); | ||||
|                     expect(controller.subscribe).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it('triggers refiltering of the columns when configuration' + | ||||
|                     ' changes', function () { | ||||
|                     controller.setup(); | ||||
|                     expect(watches['domainObject.getModel().configuration.table.columns']).toBeDefined(); | ||||
|                     watches['domainObject.getModel().configuration.table.columns'](); | ||||
|                     expect(controller.filterColumns).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|             describe('After populating columns', function () { | ||||
|                 var metadata; | ||||
|                 beforeEach(function () { | ||||
|                     metadata = [{domains: [{name: 'time domain 1'}, {name: 'time domain 2'}]}, {domains: [{name: 'time domain 3'}, {name: 'time domain 4'}]}]; | ||||
|                     controller.populateColumns(metadata); | ||||
|                 }); | ||||
|  | ||||
|                 it('Automatically identifies time columns', function () { | ||||
|                     expect(controller.timeColumns.length).toBe(4); | ||||
|                     expect(controller.timeColumns[0]).toBe('time domain 1'); | ||||
|                 }); | ||||
|  | ||||
|                 it('Automatically sorts by time column that matches current' + | ||||
|                     ' time system', function () { | ||||
|                     var key = 'time_domain_1', | ||||
|                         name = 'time domain 1', | ||||
|                         mockTimeSystem = { | ||||
|                             metadata: { | ||||
|                                 key: key | ||||
|                             } | ||||
|                         }; | ||||
|  | ||||
|                     mockTable.columns = [ | ||||
|                         { | ||||
|                             domainMetadata: { | ||||
|                                 key: key | ||||
|                             }, | ||||
|                             getTitle: function () { | ||||
|                                 return name; | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             domainMetadata: { | ||||
|                                 key: 'anotherColumn' | ||||
|                             }, | ||||
|                             getTitle: function () { | ||||
|                                 return 'some other column'; | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             domainMetadata: { | ||||
|                                 key: 'thirdColumn' | ||||
|                             }, | ||||
|                             getTitle: function () { | ||||
|                                 return 'a third column'; | ||||
|                             } | ||||
|                         } | ||||
|                     ]; | ||||
|  | ||||
|                     expect(mockConductor.on).toHaveBeenCalledWith('timeSystem', jasmine.any(Function)); | ||||
|                     getCallback(mockConductor.on, 'timeSystem')(mockTimeSystem); | ||||
|                     expect(controller.$scope.defaultSort).toBe(name); | ||||
|                 }); | ||||
|             }); | ||||
|             describe('Yields thread', function () { | ||||
|                 var mockSeries, | ||||
|                     mockRow; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     mockSeries = { | ||||
|                         getPointCount: function () { | ||||
|                             return 5; | ||||
|                         }, | ||||
|                         getDomainValue: function () { | ||||
|                             return 'Domain Value'; | ||||
|                         }, | ||||
|                         getRangeValue: function () { | ||||
|                             return 'Range Value'; | ||||
|                         } | ||||
|                     }; | ||||
|                     mockRow = {'domain': 'Domain Value', 'range': 'Range Value'}; | ||||
|  | ||||
|                     mockTelemetryHandle.makeDatum.andCallFake(function () { | ||||
|                         return mockRow; | ||||
|                     }); | ||||
|                     mockTable.getRowValues.andReturn(mockRow); | ||||
|                     mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]); | ||||
|                     mockTelemetryHandle.getSeries.andReturn(mockSeries); | ||||
|                 }); | ||||
|                 it('when row count exceeds batch size', function () { | ||||
|                     controller.batchSize = 3; | ||||
|                     controller.addHistoricalData(mockDomainObject, mockSeries); | ||||
|  | ||||
|                     //Timeout is called a minimum of two times | ||||
|                     expect(mockAngularTimeout).toHaveBeenCalled(); | ||||
|                     mockAngularTimeout.mostRecentCall.args[0](); | ||||
|  | ||||
|                     expect(mockAngularTimeout.calls.length).toEqual(2); | ||||
|                     mockAngularTimeout.mostRecentCall.args[0](); | ||||
|  | ||||
|                     //Because it yields, timeout will have been called a | ||||
|                     // third time for the batch. | ||||
|                     expect(mockAngularTimeout.calls.length).toEqual(3); | ||||
|                     mockAngularTimeout.mostRecentCall.args[0](); | ||||
|  | ||||
|                     expect(controller.$scope.rows.length).toBe(5); | ||||
|                     expect(controller.$scope.rows[0]).toBe(mockRow); | ||||
|                 }); | ||||
|                 it('cancelling any outstanding timeouts', function () { | ||||
|                     controller.batchSize = 3; | ||||
|                     controller.addHistoricalData(mockDomainObject, mockSeries); | ||||
|  | ||||
|                     expect(mockAngularTimeout).toHaveBeenCalled(); | ||||
|                     mockAngularTimeout.mostRecentCall.args[0](); | ||||
|  | ||||
|                     controller.addHistoricalData(mockDomainObject, mockSeries); | ||||
|  | ||||
|                     expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle); | ||||
|                 }); | ||||
|                 it('cancels timeout on scope destruction', function () { | ||||
|                     controller.batchSize = 3; | ||||
|                     controller.addHistoricalData(mockDomainObject, mockSeries); | ||||
|  | ||||
|                     //Destroy is used by parent class as well, so multiple | ||||
|                     // calls are made to scope.$on | ||||
|                     var destroyCalls = mockScope.$on.calls.filter(function (call) { | ||||
|                         return call.args[0] === '$destroy'; | ||||
|                     }); | ||||
|                     //Call destroy function | ||||
|                     expect(destroyCalls.length).toEqual(2); | ||||
|  | ||||
|                     destroyCalls[0].args[1](); | ||||
|                     expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle); | ||||
|  | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -39,21 +39,13 @@ define( | ||||
|             var controller, | ||||
|                 mockScope, | ||||
|                 watches, | ||||
|                 mockTimeout, | ||||
|                 mockWindow, | ||||
|                 mockElement, | ||||
|                 mockExportService, | ||||
|                 mockConductor, | ||||
|                 mockFormatService, | ||||
|                 mockFormat; | ||||
|  | ||||
|             function promise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return promise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             function getCallback(target, event) { | ||||
|                 return target.calls.filter(function (call) { | ||||
|                     return call.args[0] === event; | ||||
| @@ -66,7 +58,8 @@ define( | ||||
|                 mockScope = jasmine.createSpyObj('scope', [ | ||||
|                    '$watch', | ||||
|                    '$on', | ||||
|                    '$watchCollection' | ||||
|                    '$watchCollection', | ||||
|                    '$digest' | ||||
|                 ]); | ||||
|                 mockScope.$watchCollection.andCallFake(function (event, callback) { | ||||
|                     watches[event] = callback; | ||||
| @@ -86,8 +79,11 @@ define( | ||||
|                 ]); | ||||
|  | ||||
|                 mockScope.displayHeaders = true; | ||||
|                 mockTimeout = jasmine.createSpy('$timeout'); | ||||
|                 mockTimeout.andReturn(promise(undefined)); | ||||
|                 mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']); | ||||
|                 mockWindow.requestAnimationFrame.andCallFake(function (f) { | ||||
|                     return f(); | ||||
|                 }); | ||||
|  | ||||
|                 mockFormat = jasmine.createSpyObj('formatter', [ | ||||
|                     'parse', | ||||
|                     'format' | ||||
| @@ -99,7 +95,7 @@ define( | ||||
|  | ||||
|                 controller = new MCTTableController( | ||||
|                     mockScope, | ||||
|                     mockTimeout, | ||||
|                     mockWindow, | ||||
|                     mockElement, | ||||
|                     mockExportService, | ||||
|                     mockFormatService, | ||||
| @@ -114,12 +110,12 @@ define( | ||||
|                 expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             it('destroys listeners on destruction', function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith('$destroy', controller.destroyConductorListeners); | ||||
|             it('unregisters listeners on destruction', function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function)); | ||||
|                 getCallback(mockScope.$on, '$destroy')(); | ||||
|  | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.setTimeOfInterest); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds); | ||||
|             }); | ||||
|  | ||||
| @@ -233,9 +229,20 @@ define( | ||||
|  | ||||
|                         //Mock setting the rows on scope | ||||
|                         var rowsCallback = getCallback(mockScope.$watch, 'rows'); | ||||
|                         rowsCallback(rowsAsc); | ||||
|                         var setRowsPromise = rowsCallback(rowsAsc); | ||||
|                         var promiseResolved = false; | ||||
|                         setRowsPromise.then(function () { | ||||
|                             promiseResolved = true; | ||||
|                         }); | ||||
|  | ||||
|                         waitsFor(function () { | ||||
|                             return promiseResolved; | ||||
|                         }, "promise to resolve", 100); | ||||
|  | ||||
|                         runs(function () { | ||||
|                             expect(mockScope.toiRowIndex).toBe(2); | ||||
|                         }); | ||||
|  | ||||
|                         expect(mockScope.toiRowIndex).toBe(2); | ||||
|                     }); | ||||
|  | ||||
|                 }); | ||||
| @@ -287,7 +294,7 @@ define( | ||||
|                 }); | ||||
|  | ||||
|                 it('Supports adding rows individually', function () { | ||||
|                     var addRowFunc = getCallback(mockScope.$on, 'add:row'), | ||||
|                     var addRowFunc = getCallback(mockScope.$on, 'add:rows'), | ||||
|                         row4 = { | ||||
|                             'col1': {'text': 'row3 col1'}, | ||||
|                             'col2': {'text': 'ghi'}, | ||||
| @@ -296,15 +303,15 @@ define( | ||||
|                     controller.setRows(testRows); | ||||
|                     expect(mockScope.displayRows.length).toBe(3); | ||||
|                     testRows.push(row4); | ||||
|                     addRowFunc(undefined, 3); | ||||
|                     addRowFunc(undefined, [row4]); | ||||
|                     expect(mockScope.displayRows.length).toBe(4); | ||||
|                 }); | ||||
|  | ||||
|                 it('Supports removing rows individually', function () { | ||||
|                     var removeRowFunc = getCallback(mockScope.$on, 'remove:row'); | ||||
|                     var removeRowFunc = getCallback(mockScope.$on, 'remove:rows'); | ||||
|                     controller.setRows(testRows); | ||||
|                     expect(mockScope.displayRows.length).toBe(3); | ||||
|                     removeRowFunc(undefined, 2); | ||||
|                     removeRowFunc(undefined, [testRows[2]]); | ||||
|                     expect(mockScope.displayRows.length).toBe(2); | ||||
|                     expect(controller.setVisibleRows).toHaveBeenCalled(); | ||||
|                 }); | ||||
| @@ -366,7 +373,7 @@ define( | ||||
|                     it('Allows sort column to be changed externally by ' + | ||||
|                        'setting or changing sortBy attribute', function () { | ||||
|                         mockScope.displayRows = testRows; | ||||
|                         var sortByCB = getCallback(mockScope.$watch, 'sortColumn'); | ||||
|                         var sortByCB = getCallback(mockScope.$watch, 'defaultSort'); | ||||
|                         sortByCB('col2'); | ||||
|  | ||||
|                         expect(mockScope.sortDirection).toEqual('asc'); | ||||
| @@ -381,10 +388,21 @@ define( | ||||
|                     it('updates visible rows in scope', function () { | ||||
|                         var oldRows; | ||||
|                         mockScope.rows = testRows; | ||||
|                         controller.setRows(testRows); | ||||
|                         var setRowsPromise = controller.setRows(testRows); | ||||
|                         var promiseResolved = false; | ||||
|                         setRowsPromise.then(function () { | ||||
|                             promiseResolved = true; | ||||
|                         }); | ||||
|                         oldRows = mockScope.visibleRows; | ||||
|                         mockScope.toggleSort('col2'); | ||||
|                         expect(mockScope.visibleRows).not.toEqual(oldRows); | ||||
|  | ||||
|                         waitsFor(function () { | ||||
|                             return promiseResolved; | ||||
|                         }, "promise to resolve", 100); | ||||
|  | ||||
|                         runs(function () { | ||||
|                             expect(mockScope.visibleRows).not.toEqual(oldRows); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it('correctly sorts rows of differing types', function () { | ||||
| @@ -464,21 +482,10 @@ define( | ||||
|  | ||||
|                             mockScope.displayRows = controller.sortRows(testRows.slice(0)); | ||||
|  | ||||
|                             mockScope.rows.push(row4); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row4, row5, row6, row6]); | ||||
|                             expect(mockScope.displayRows[0].col2.text).toEqual('xyz'); | ||||
|  | ||||
|                             mockScope.rows.push(row5); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             expect(mockScope.displayRows[4].col2.text).toEqual('aaa'); | ||||
|  | ||||
|                             mockScope.rows.push(row6); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             expect(mockScope.displayRows[2].col2.text).toEqual('ggg'); | ||||
|  | ||||
|                             //Add a duplicate row | ||||
|                             mockScope.rows.push(row6); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             expect(mockScope.displayRows[6].col2.text).toEqual('aaa'); | ||||
|                             //Added a duplicate row | ||||
|                             expect(mockScope.displayRows[2].col2.text).toEqual('ggg'); | ||||
|                             expect(mockScope.displayRows[3].col2.text).toEqual('ggg'); | ||||
|                         }); | ||||
| @@ -493,13 +500,11 @@ define( | ||||
|                             mockScope.displayRows = controller.sortRows(testRows.slice(0)); | ||||
|                             mockScope.displayRows = controller.filterRows(testRows); | ||||
|  | ||||
|                             mockScope.rows.push(row5); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row5]); | ||||
|                             expect(mockScope.displayRows.length).toBe(2); | ||||
|                             expect(mockScope.displayRows[1].col2.text).toEqual('aaa'); | ||||
|  | ||||
|                             mockScope.rows.push(row6); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row6]); | ||||
|                             expect(mockScope.displayRows.length).toBe(2); | ||||
|                             //Row was not added because does not match filter | ||||
|                         }); | ||||
| @@ -512,12 +517,10 @@ define( | ||||
|  | ||||
|                             mockScope.displayRows = testRows.slice(0); | ||||
|  | ||||
|                             mockScope.rows.push(row5); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row5]); | ||||
|                             expect(mockScope.displayRows[3].col2.text).toEqual('aaa'); | ||||
|  | ||||
|                             mockScope.rows.push(row6); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row6]); | ||||
|                             expect(mockScope.displayRows[4].col2.text).toEqual('ggg'); | ||||
|                         }); | ||||
|  | ||||
| @@ -535,8 +538,7 @@ define( | ||||
|  | ||||
|                             mockScope.displayRows = testRows.slice(0); | ||||
|  | ||||
|                             mockScope.rows.push(row7); | ||||
|                             controller.addRow(undefined, mockScope.rows.length - 1); | ||||
|                             controller.addRows(undefined, [row7]); | ||||
|                             expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'}); | ||||
|                         }); | ||||
|  | ||||
|   | ||||
| @@ -1,171 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "../../src/controllers/RealtimeTableController" | ||||
|     ], | ||||
|     function (TableController) { | ||||
|  | ||||
|         describe('The real-time table controller', function () { | ||||
|             var mockScope, | ||||
|                 mockTelemetryHandler, | ||||
|                 mockTelemetryHandle, | ||||
|                 mockTelemetryFormatter, | ||||
|                 mockDomainObject, | ||||
|                 mockTable, | ||||
|                 mockConfiguration, | ||||
|                 watches, | ||||
|                 mockTableRow, | ||||
|                 mockConductor, | ||||
|                 controller; | ||||
|  | ||||
|             function promise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return promise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 watches = {}; | ||||
|                 mockTableRow = {'col1': 'val1', 'col2': 'row2'}; | ||||
|  | ||||
|                 mockScope = jasmine.createSpyObj('scope', [ | ||||
|                     '$on', | ||||
|                     '$watch', | ||||
|                     '$watchCollection', | ||||
|                     '$digest', | ||||
|                     '$broadcast' | ||||
|                 ]); | ||||
|                 mockScope.$on.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|                 mockScope.$watch.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|                 mockScope.$watchCollection.andCallFake(function (expression, callback) { | ||||
|                     watches[expression] = callback; | ||||
|                 }); | ||||
|  | ||||
|                 mockConfiguration = { | ||||
|                     'range1': true, | ||||
|                     'range2': true, | ||||
|                     'domain1': true | ||||
|                 }; | ||||
|  | ||||
|                 mockTable = jasmine.createSpyObj('table', | ||||
|                     [ | ||||
|                         'populateColumns', | ||||
|                         'buildColumnConfiguration', | ||||
|                         'getRowValues', | ||||
|                         'saveColumnConfiguration' | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockTable.columns = []; | ||||
|                 mockTable.buildColumnConfiguration.andReturn(mockConfiguration); | ||||
|                 mockTable.getRowValues.andReturn(mockTableRow); | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj('domainObject', [ | ||||
|                     'getCapability', | ||||
|                     'useCapability', | ||||
|                     'getModel' | ||||
|                 ]); | ||||
|                 mockDomainObject.getModel.andReturn({}); | ||||
|                 mockDomainObject.getCapability.andReturn( | ||||
|                     { | ||||
|                         getMetadata: function () { | ||||
|                             return {ranges: [{format: 'string'}]}; | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                 mockScope.domainObject = mockDomainObject; | ||||
|  | ||||
|                 mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [ | ||||
|                     'getMetadata', | ||||
|                     'unsubscribe', | ||||
|                     'getDatum', | ||||
|                     'promiseTelemetryObjects', | ||||
|                     'getTelemetryObjects', | ||||
|                     'request', | ||||
|                     'getMetadata' | ||||
|                 ]); | ||||
|  | ||||
|                 // Arbitrary array with non-zero length, contents are not | ||||
|                 // used by mocks | ||||
|                 mockTelemetryHandle.getTelemetryObjects.andReturn([{}]); | ||||
|                 mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined)); | ||||
|                 mockTelemetryHandle.getDatum.andReturn({}); | ||||
|                 mockTelemetryHandle.request.andReturn(promise(undefined)); | ||||
|                 mockTelemetryHandle.getMetadata.andReturn([]); | ||||
|  | ||||
|                 mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [ | ||||
|                     'handle' | ||||
|                 ]); | ||||
|                 mockTelemetryHandler.handle.andReturn(mockTelemetryHandle); | ||||
|  | ||||
|                 mockConductor = jasmine.createSpyObj('conductor', [ | ||||
|                     'on', | ||||
|                     'off', | ||||
|                     'bounds', | ||||
|                     'timeSystem', | ||||
|                     'timeOfInterest' | ||||
|                 ]); | ||||
|  | ||||
|                 controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, {conductor: mockConductor}); | ||||
|                 controller.table = mockTable; | ||||
|                 controller.handle = mockTelemetryHandle; | ||||
|             }); | ||||
|  | ||||
|             it('registers for streaming telemetry', function () { | ||||
|                 controller.subscribe(); | ||||
|                 expect(mockTelemetryHandler.handle).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function), true); | ||||
|             }); | ||||
|  | ||||
|             describe('receives new telemetry', function () { | ||||
|                 beforeEach(function () { | ||||
|                     controller.subscribe(); | ||||
|                     mockScope.rows = []; | ||||
|                 }); | ||||
|  | ||||
|                 it('updates table with new streaming telemetry', function () { | ||||
|                     mockTelemetryHandler.handle.mostRecentCall.args[1](); | ||||
|                     expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0); | ||||
|                 }); | ||||
|                 it('observes the row limit', function () { | ||||
|                     var i = 0; | ||||
|                     controller.maxRows = 10; | ||||
|  | ||||
|                     //Fill rows array with elements | ||||
|                     for (; i < 10; i++) { | ||||
|                         mockScope.rows.push({row: i}); | ||||
|                     } | ||||
|                     mockTelemetryHandler.handle.mostRecentCall.args[1](); | ||||
|                     expect(mockScope.rows.length).toBe(controller.maxRows); | ||||
|                     expect(mockScope.rows[mockScope.rows.length - 1]).toBe(mockTableRow); | ||||
|                     expect(mockScope.rows[0].row).toBe(1); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -0,0 +1,364 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         '../../src/controllers/TelemetryTableController', | ||||
|         '../../../../../src/api/objects/object-utils', | ||||
|         'lodash' | ||||
|     ], | ||||
|     function (TelemetryTableController, objectUtils, _) { | ||||
|  | ||||
|         describe('The TelemetryTableController', function () { | ||||
|  | ||||
|             var controller, | ||||
|                 mockScope, | ||||
|                 mockTimeout, | ||||
|                 mockConductor, | ||||
|                 mockAPI, | ||||
|                 mockDomainObject, | ||||
|                 mockTelemetryAPI, | ||||
|                 mockObjectAPI, | ||||
|                 mockCompositionAPI, | ||||
|                 unobserve, | ||||
|                 mockBounds; | ||||
|  | ||||
|             function getCallback(target, event) { | ||||
|                 return target.calls.filter(function (call) { | ||||
|                     return call.args[0] === event; | ||||
|                 })[0].args[1]; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockBounds = { | ||||
|                     start: 0, | ||||
|                     end: 10 | ||||
|                 }; | ||||
|                 mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                     "bounds", | ||||
|                     "follow", | ||||
|                     "on", | ||||
|                     "off", | ||||
|                     "timeSystem" | ||||
|                 ]); | ||||
|                 mockConductor.bounds.andReturn(mockBounds); | ||||
|                 mockConductor.follow.andReturn(false); | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj("domainObject", [ | ||||
|                     "getModel", | ||||
|                     "getId", | ||||
|                     "useCapability" | ||||
|                 ]); | ||||
|                 mockDomainObject.getModel.andReturn({}); | ||||
|                 mockDomainObject.getId.andReturn("mockId"); | ||||
|                 mockDomainObject.useCapability.andReturn(true); | ||||
|  | ||||
|                 mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [ | ||||
|                     "get" | ||||
|                 ]); | ||||
|  | ||||
|                 mockObjectAPI = jasmine.createSpyObj("objectAPI", [ | ||||
|                     "observe" | ||||
|                 ]); | ||||
|                 unobserve = jasmine.createSpy("unobserve"); | ||||
|                 mockObjectAPI.observe.andReturn(unobserve); | ||||
|  | ||||
|                 mockScope = jasmine.createSpyObj("scope", [ | ||||
|                     "$on", | ||||
|                     "$watch", | ||||
|                     "$broadcast" | ||||
|                 ]); | ||||
|                 mockScope.domainObject = mockDomainObject; | ||||
|  | ||||
|                 mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [ | ||||
|                     "canProvideTelemetry", | ||||
|                     "subscribe", | ||||
|                     "getMetadata", | ||||
|                     "commonValuesForHints", | ||||
|                     "request", | ||||
|                     "limitEvaluator", | ||||
|                     "getValueFormatter" | ||||
|                 ]); | ||||
|                 mockTelemetryAPI.commonValuesForHints.andReturn([]); | ||||
|                 mockTelemetryAPI.request.andReturn(Promise.resolve([])); | ||||
|  | ||||
|  | ||||
|                 mockTelemetryAPI.canProvideTelemetry.andReturn(false); | ||||
|  | ||||
|                 mockTimeout = jasmine.createSpy("timeout"); | ||||
|                 mockTimeout.andReturn(1); // Return something | ||||
|                 mockTimeout.cancel = jasmine.createSpy("cancel"); | ||||
|  | ||||
|                 mockAPI = { | ||||
|                     conductor: mockConductor, | ||||
|                     objects: mockObjectAPI, | ||||
|                     telemetry: mockTelemetryAPI, | ||||
|                     composition: mockCompositionAPI | ||||
|                 }; | ||||
|                 controller = new TelemetryTableController(mockScope, mockTimeout, mockAPI); | ||||
|             }); | ||||
|  | ||||
|             describe('listens for', function () { | ||||
|                 beforeEach(function () { | ||||
|                     controller.registerChangeListeners(); | ||||
|                 }); | ||||
|                 it('object mutation', function () { | ||||
|                     var calledObject = mockObjectAPI.observe.mostRecentCall.args[0]; | ||||
|  | ||||
|                     expect(mockObjectAPI.observe).toHaveBeenCalled(); | ||||
|                     expect(calledObject.identifier.key).toEqual(mockDomainObject.getId()); | ||||
|                 }); | ||||
|                 it('conductor changes', function () { | ||||
|                     expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function)); | ||||
|                     expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function)); | ||||
|                     expect(mockConductor.on).toHaveBeenCalledWith("follow", jasmine.any(Function)); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe('deregisters all listeners on scope destruction', function () { | ||||
|                 var timeSystemListener, | ||||
|                     boundsListener, | ||||
|                     followListener; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     controller.registerChangeListeners(); | ||||
|  | ||||
|                     timeSystemListener = getCallback(mockConductor.on, "timeSystem"); | ||||
|                     boundsListener = getCallback(mockConductor.on, "bounds"); | ||||
|                     followListener = getCallback(mockConductor.on, "follow"); | ||||
|  | ||||
|                     var destroy = getCallback(mockScope.$on, "$destroy"); | ||||
|                     destroy(); | ||||
|                 }); | ||||
|  | ||||
|                 it('object mutation', function () { | ||||
|                     expect(unobserve).toHaveBeenCalled(); | ||||
|                 }); | ||||
|                 it('conductor changes', function () { | ||||
|                     expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener); | ||||
|                     expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener); | ||||
|                     expect(mockConductor.off).toHaveBeenCalledWith("follow", followListener); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe ('Subscribes to new data', function () { | ||||
|                 var mockComposition, | ||||
|                     mockTelemetryObject, | ||||
|                     mockChildren, | ||||
|                     unsubscribe, | ||||
|                     done; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     mockComposition = jasmine.createSpyObj("composition", [ | ||||
|                         "load" | ||||
|                     ]); | ||||
|  | ||||
|                     mockTelemetryObject = jasmine.createSpyObj("mockTelemetryObject", [ | ||||
|                         "something" | ||||
|                     ]); | ||||
|                     mockTelemetryObject.identifier = { | ||||
|                         key: "mockTelemetryObject" | ||||
|                     }; | ||||
|  | ||||
|                     unsubscribe = jasmine.createSpy("unsubscribe"); | ||||
|                     mockTelemetryAPI.subscribe.andReturn(unsubscribe); | ||||
|  | ||||
|                     mockChildren = [mockTelemetryObject]; | ||||
|                     mockComposition.load.andReturn(Promise.resolve(mockChildren)); | ||||
|                     mockCompositionAPI.get.andReturn(mockComposition); | ||||
|  | ||||
|                     mockTelemetryAPI.canProvideTelemetry.andCallFake(function (obj) { | ||||
|                         return obj.identifier.key === mockTelemetryObject.identifier.key; | ||||
|                     }); | ||||
|  | ||||
|                     done = false; | ||||
|                     controller.getData().then(function () { | ||||
|                         done = true; | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it('fetches historical data', function () { | ||||
|                     waitsFor(function () { | ||||
|                         return done; | ||||
|                     }, "getData to return", 100); | ||||
|  | ||||
|                     runs(function () { | ||||
|                         expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object)); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it('fetches historical data for the time period specified by the conductor bounds', function () { | ||||
|                     waitsFor(function () { | ||||
|                         return done; | ||||
|                     }, "getData to return", 100); | ||||
|  | ||||
|                     runs(function () { | ||||
|                         expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it('subscribes to new data', function () { | ||||
|                     waitsFor(function () { | ||||
|                         return done; | ||||
|                     }, "getData to return", 100); | ||||
|  | ||||
|                     runs(function () { | ||||
|                         expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {}); | ||||
|                     }); | ||||
|  | ||||
|                 }); | ||||
|                 it('and unsubscribes on view destruction', function () { | ||||
|                     waitsFor(function () { | ||||
|                         return done; | ||||
|                     }, "getData to return", 100); | ||||
|  | ||||
|                     runs(function () { | ||||
|                         var destroy = getCallback(mockScope.$on, "$destroy"); | ||||
|                         destroy(); | ||||
|  | ||||
|                         expect(unsubscribe).toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it('When in real-time mode, enables auto-scroll', function () { | ||||
|                 controller.registerChangeListeners(); | ||||
|  | ||||
|                 var followCallback = getCallback(mockConductor.on, "follow"); | ||||
|                 //Confirm pre-condition | ||||
|                 expect(mockScope.autoScroll).toBeFalsy(); | ||||
|  | ||||
|                 //Mock setting the conductor to 'follow' mode | ||||
|                 followCallback(true); | ||||
|                 expect(mockScope.autoScroll).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             describe('populates table columns', function () { | ||||
|                 var domainMetadata; | ||||
|                 var allMetadata; | ||||
|                 var mockTimeSystem; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     domainMetadata = [{ | ||||
|                         key: "column1", | ||||
|                         name: "Column 1", | ||||
|                         hints: {} | ||||
|                     }]; | ||||
|  | ||||
|                     allMetadata = [{ | ||||
|                         key: "column1", | ||||
|                         name: "Column 1", | ||||
|                         hints: {} | ||||
|                     }, { | ||||
|                         key: "column2", | ||||
|                         name: "Column 2", | ||||
|                         hints: {} | ||||
|                     }, { | ||||
|                         key: "column3", | ||||
|                         name: "Column 3", | ||||
|                         hints: {} | ||||
|                     }]; | ||||
|  | ||||
|                     mockTimeSystem = { | ||||
|                         metadata: { | ||||
|                             key: "column1" | ||||
|                         } | ||||
|                     }; | ||||
|  | ||||
|                     mockTelemetryAPI.commonValuesForHints.andCallFake(function (metadata, hints) { | ||||
|                         if (_.eq(hints, ["x"])) { | ||||
|                             return domainMetadata; | ||||
|                         } else if (_.eq(hints, [])) { | ||||
|                             return allMetadata; | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     controller.loadColumns([mockDomainObject]); | ||||
|                 }); | ||||
|  | ||||
|                 it('based on metadata for given objects', function () { | ||||
|                     expect(mockScope.headers).toBeDefined(); | ||||
|                     expect(mockScope.headers.length).toBeGreaterThan(0); | ||||
|                     expect(mockScope.headers.indexOf(allMetadata[0].name)).not.toBe(-1); | ||||
|                     expect(mockScope.headers.indexOf(allMetadata[1].name)).not.toBe(-1); | ||||
|                     expect(mockScope.headers.indexOf(allMetadata[2].name)).not.toBe(-1); | ||||
|                 }); | ||||
|  | ||||
|                 it('and sorts by column matching time system', function () { | ||||
|                     expect(mockScope.defaultSort).not.toEqual("Column 1"); | ||||
|                     controller.sortByTimeSystem(mockTimeSystem); | ||||
|                     expect(mockScope.defaultSort).toEqual("Column 1"); | ||||
|                 }); | ||||
|  | ||||
|                 it('batches processing of rows for performance when receiving historical telemetry', function () { | ||||
|                     var mockHistoricalData = [ | ||||
|                         { | ||||
|                             "column1": 1, | ||||
|                             "column2": 2, | ||||
|                             "column3": 3 | ||||
|                         },{ | ||||
|                             "column1": 4, | ||||
|                             "column2": 5, | ||||
|                             "column3": 6 | ||||
|                         }, { | ||||
|                             "column1": 7, | ||||
|                             "column2": 8, | ||||
|                             "column3": 9 | ||||
|                         } | ||||
|                     ]; | ||||
|                     controller.batchSize = 2; | ||||
|                     mockTelemetryAPI.request.andReturn(Promise.resolve(mockHistoricalData)); | ||||
|                     controller.getHistoricalData([mockDomainObject]); | ||||
|  | ||||
|                     waitsFor(function () { | ||||
|                         return !!controller.timeoutHandle; | ||||
|                     }, "first batch to be processed", 100); | ||||
|  | ||||
|                     runs(function () { | ||||
|                         //Verify that timeout is being used to yield process | ||||
|                         expect(mockTimeout).toHaveBeenCalled(); | ||||
|                         mockTimeout.mostRecentCall.args[0](); | ||||
|                         expect(mockTimeout.calls.length).toBe(2); | ||||
|                         mockTimeout.mostRecentCall.args[0](); | ||||
|                         expect(mockScope.rows.length).toBe(3); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it('Removes telemetry rows from table when they fall out of bounds', function () { | ||||
|                 var discardedRows = [ | ||||
|                     {"column1": "value 1"}, | ||||
|                     {"column2": "value 2"}, | ||||
|                     {"column3": "value 3"} | ||||
|                 ]; | ||||
|  | ||||
|                 spyOn(controller.telemetry, "on").andCallThrough(); | ||||
|  | ||||
|                 controller.registerChangeListeners(); | ||||
|                 expect(controller.telemetry.on).toHaveBeenCalledWith("discarded", jasmine.any(Function)); | ||||
|                 var onDiscard = getCallback(controller.telemetry.on, "discarded"); | ||||
|                 onDiscard(discardedRows); | ||||
|                 expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     }); | ||||
| @@ -153,7 +153,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "timeline", | ||||
|                     "name": "Timeline", | ||||
|                     "cssclass": "icon-timeline", | ||||
|                     "cssClass": "icon-timeline", | ||||
|                     "description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.", | ||||
|                     "priority": 502, | ||||
|                     "features": [ | ||||
| @@ -206,7 +206,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "activity", | ||||
|                     "name": "Activity", | ||||
|                     "cssclass": "icon-activity", | ||||
|                     "cssClass": "icon-activity", | ||||
|                     "features": [ | ||||
|                         "creation" | ||||
|                     ], | ||||
| @@ -252,7 +252,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "mode", | ||||
|                     "name": "Activity Mode", | ||||
|                     "cssclass": "icon-activity-mode", | ||||
|                     "cssClass": "icon-activity-mode", | ||||
|                     "features": [ | ||||
|                         "creation" | ||||
|                     ], | ||||
| @@ -292,7 +292,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "values", | ||||
|                     "name": "Values", | ||||
|                     "cssclass": "icon-activity-mode", | ||||
|                     "cssClass": "icon-activity-mode", | ||||
|                     "template": valuesTemplate, | ||||
|                     "type": "mode", | ||||
|                     "uses": [ | ||||
| @@ -303,7 +303,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "timeline", | ||||
|                     "name": "Timeline", | ||||
|                     "cssclass": "icon-timeline", | ||||
|                     "cssClass": "icon-timeline", | ||||
|                     "type": "timeline", | ||||
|                     "description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.", | ||||
|                     "template": timelineTemplate, | ||||
| @@ -319,12 +319,12 @@ define([ | ||||
|                                         "options": [ | ||||
|                                             { | ||||
|                                                 "name": "Timeline", | ||||
|                                                 "cssclass": "icon-timeline", | ||||
|                                                 "cssClass": "icon-timeline", | ||||
|                                                 "key": "timeline" | ||||
|                                             }, | ||||
|                                             { | ||||
|                                                 "name": "Activity", | ||||
|                                                 "cssclass": "icon-activity", | ||||
|                                                 "cssClass": "icon-activity", | ||||
|                                                 "key": "activity" | ||||
|                                             } | ||||
|                                         ] | ||||
| @@ -334,13 +334,13 @@ define([ | ||||
|                             { | ||||
|                                 "items": [ | ||||
|                                     { | ||||
|                                         "cssclass": "icon-plot-resource", | ||||
|                                         "cssClass": "icon-plot-resource", | ||||
|                                         "description": "Graph Resource Utilization", | ||||
|                                         "control": "button", | ||||
|                                         "method": "toggleGraph" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "cssclass": "icon-activity-mode", | ||||
|                                         "cssClass": "icon-activity-mode", | ||||
|                                         "control": "dialog-button", | ||||
|                                         "description": "Apply Activity Modes...", | ||||
|                                         "title": "Apply Activity Modes", | ||||
| @@ -353,7 +353,7 @@ define([ | ||||
|                                         "property": "modes" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "cssclass": "icon-chain-links", | ||||
|                                         "cssClass": "icon-chain-links", | ||||
|                                         "description": "Edit Activity Link", | ||||
|                                         "title": "Activity Link", | ||||
|                                         "control": "dialog-button", | ||||
| @@ -361,12 +361,12 @@ define([ | ||||
|                                             "control": "textfield", | ||||
|                                             "name": "Link", | ||||
|                                             "pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$", | ||||
|                                             "cssclass": "l-input-lg" | ||||
|                                             "cssClass": "l-input-lg" | ||||
|                                         }, | ||||
|                                         "property": "link" | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         "cssclass": "icon-gear", | ||||
|                                         "cssClass": "icon-gear", | ||||
|                                         "description": "Edit Properties...", | ||||
|                                         "control": "button", | ||||
|                                         "method": "properties" | ||||
| @@ -379,7 +379,7 @@ define([ | ||||
|                                         "method": "remove", | ||||
|                                         "description": "Remove Item", | ||||
|                                         "control": "button", | ||||
|                                         "cssclass": "icon-trash" | ||||
|                                         "cssClass": "icon-trash" | ||||
|                                     } | ||||
|                                 ] | ||||
|                             } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <a class="s-button {{structure.cssclass}}" | ||||
| <a class="s-button {{structure.cssClass}}" | ||||
|    ng-class="{ labeled: structure.text }" | ||||
|    ng-click="structure.click()"> | ||||
|     <span class="title-label" ng-if="structure.text"> | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="s-button s-menu-button menu-element t-color-palette {{structure.cssclass}}" | ||||
| <div class="s-button s-menu-button menu-element t-color-palette {{structure.cssClass}}" | ||||
|     ng-controller="ClickAwayController as toggle"> | ||||
|  | ||||
|     <span class="l-click-area" ng-click="toggle.toggle()"></span> | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
| --> | ||||
| <span ng-controller="CompositeController as compositeCtrl"> | ||||
|     <ng-form name="mctFormItem" ng-repeat="item in structure.items"> | ||||
|         <div class="l-composite-control l-{{item.control}} {{item.cssclass}}"> | ||||
|         <div class="l-composite-control l-{{item.control}} {{item.cssClass}}"> | ||||
|             <mct-control key="item.control" | ||||
|                          ng-model="ngModel[field]" | ||||
|                          ng-required="ngRequired || compositeCtrl.isNonEmpty(ngModel[field])" | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="s-menu-button menu-element {{ structure.cssclass }}" | ||||
| <div class="s-menu-button menu-element {{ structure.cssClass }}" | ||||
|      ng-controller="ClickAwayController as toggle"> | ||||
|  | ||||
|     <span class="l-click-area" ng-click="toggle.toggle()"></span> | ||||
| @@ -31,7 +31,7 @@ | ||||
|         <ul> | ||||
|             <li ng-click="structure.click(option.key); toggle.setState(false)" | ||||
|                 ng-repeat="option in structure.options" | ||||
|                 class="{{ option.cssclass }}"> | ||||
|                 class="{{ option.cssClass }}"> | ||||
|                     {{option.name}} | ||||
|             </li> | ||||
|         </ul> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span class='form-control shell'> | ||||
|     <span class='field control {{structure.cssclass}}'> | ||||
|     <span class='field control {{structure.cssClass}}'> | ||||
|         <textarea ng-required="ngRequired" | ||||
|                ng-model="ngModel[field]" | ||||
|                ng-pattern="ngPattern" | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span class='form-control shell'> | ||||
|     <span class='field control {{structure.cssclass}}'> | ||||
|     <span class='field control {{structure.cssClass}}'> | ||||
|         <input type="text" | ||||
|                ng-required="ngRequired" | ||||
|                ng-model="ngModel[field]" | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user