Compare commits
	
		
			5 Commits
		
	
	
		
			omm-large-
			...
			latest-tel
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 12a4b2395a | ||
|   | c179098227 | ||
|   | 600d24e3af | ||
|   | 9e360fb226 | ||
|   | 6687a83ca5 | 
							
								
								
									
										11
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								index.html
									
									
									
									
									
								
							| @@ -45,8 +45,17 @@ | ||||
|                 openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|                 openmct.install(openmct.plugins.ImportExport()); | ||||
|                 openmct.install(openmct.plugins.AutoflowView({ | ||||
|                     type: "telemetry.panel" | ||||
|                     type: "view.autoflow" | ||||
|                 })); | ||||
|                 openmct.types.addType("view.autoflow", { | ||||
|                     key: "view.autoflow", | ||||
|                     name: "Autoflow", | ||||
|                     creatable: true, | ||||
|                     initialize: function (model) { | ||||
|                         model.composition = []; | ||||
|                         return model | ||||
|                     } | ||||
|                 }); | ||||
|                 openmct.install(openmct.plugins.Conductor({ | ||||
|                     menuOptions: [ | ||||
|                         { | ||||
|   | ||||
| @@ -24,12 +24,14 @@ define([ | ||||
|     './TelemetryMetadataManager', | ||||
|     './TelemetryValueFormatter', | ||||
|     './DefaultMetadataProvider', | ||||
|     './latestValueSubscription', | ||||
|     '../objects/object-utils', | ||||
|     'lodash' | ||||
| ], function ( | ||||
|     TelemetryMetadataManager, | ||||
|     TelemetryValueFormatter, | ||||
|     DefaultMetadataProvider, | ||||
|     latestValueSubscription, | ||||
|     objectUtils, | ||||
|     _ | ||||
| ) { | ||||
| @@ -335,6 +337,41 @@ define([ | ||||
|         }.bind(this); | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Subscribe to receive the latest telemetry value for a given domain | ||||
|      * object. The callback will be called whenever newer data is received from | ||||
|      * a realtime provider.  If a LAD provider is available, Open MCT will use | ||||
|      * it to provide an initial value for the latest data subscriber. | ||||
|      * | ||||
|      * Using this method will ensure that you only receive telemetry values in | ||||
|      * order, according to the current time system.  If openmct receives a new | ||||
|      * telemetry value from a provider that occurs out of order, i.e. the | ||||
|      * timestamp is less than the last received timestamp, then it will discard | ||||
|      * the message instead of notifying the callback.  In a telemetry system | ||||
|      * where data may be processed out of order, this guarantees that the end | ||||
|      * user is always viewing the latest data. | ||||
|      * | ||||
|      * If the user changes the time system, Open MCT will attempt to provide | ||||
|      * a new value from the LAD data provider and continue to provide new values | ||||
|      * via realtime providers. | ||||
|      * | ||||
|      * @param {module:openmct.DomainObject} domainObject the object | ||||
|      *        which has associated telemetry | ||||
|      * @param {Function} callback the callback to invoke with new data, as | ||||
|      *        it becomes available | ||||
|      * @returns {Function} a function which may be called to terminate | ||||
|      *          the subscription | ||||
|      */ | ||||
|     TelemetryAPI.prototype.latest = function (domainObject, callback) { | ||||
|         return latestValueSubscription( | ||||
|             domainObject, | ||||
|             callback, | ||||
|             this, | ||||
|             this.openmct | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Get telemetry metadata for a given domain object.  Returns a telemetry | ||||
|      * metadata manager which provides methods for interrogating telemetry | ||||
|   | ||||
							
								
								
									
										177
									
								
								src/api/telemetry/latestValueSubscription.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/api/telemetry/latestValueSubscription.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open openmct 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 openmct 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 ( | ||||
|  | ||||
| ) { | ||||
|  | ||||
|     /** | ||||
|      * Subscribe to receive the latest telemetry value for a given domain | ||||
|      * object. The callback will be called whenever newer data is received from | ||||
|      * a realtime provider.  If a LAD provider is available, Open MCT will use | ||||
|      * it to provide an initial value for the latest data subscriber. | ||||
|      * | ||||
|      * Using this method will ensure that you only receive telemetry values in | ||||
|      * order, according to the current time system.  If openmct receives a new | ||||
|      * telemetry value from a provider that occurs out of order, i.e. the | ||||
|      * timestamp is less than the last received timestamp, then it will discard | ||||
|      * the message instead of notifying the callback.  In a telemetry system | ||||
|      * where data may be processed out of order, this guarantees that the end | ||||
|      * user is always viewing the latest data. | ||||
|      * | ||||
|      * If the user changes the time system, Open MCT will attempt to provide | ||||
|      * a new value from the LAD data provider and continue to provide new values | ||||
|      * via realtime providers. | ||||
|      * | ||||
|      * @param {module:openmct.DomainObject} domainObject the object | ||||
|      *        which has associated telemetry | ||||
|      * @param {Function} callback the callback to invoke with new data, as | ||||
|      *        it becomes available | ||||
|      * @returns {Function} a function which may be called to terminate | ||||
|      *          the subscription | ||||
|      */ | ||||
|     function latestValueSubscription( | ||||
|         domainObject, | ||||
|         callback, | ||||
|         telemetryAPI, | ||||
|         openmct | ||||
|     ) { | ||||
|         var latestDatum; | ||||
|         var pendingRealtimeDatum; | ||||
|         var currentRequest = 0; | ||||
|         var metadata = telemetryAPI.getMetadata(domainObject); | ||||
|         var formatters = telemetryAPI.getFormatMap(metadata); | ||||
|         var timeFormatter; | ||||
|         var active = true; | ||||
|         var restrictToBounds = false; | ||||
|  | ||||
|         function isLater(a, b) { | ||||
|             return !timeFormatter || timeFormatter.parse(a) > timeFormatter.parse(b); | ||||
|         } | ||||
|  | ||||
|         function applyBoundsFilter(datum) { | ||||
|             if (withinBounds(datum)) { | ||||
|                 callback(datum); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function withinBounds(datum) { | ||||
|             if (!restrictToBounds || !timeFormatter) { | ||||
|                 return true; | ||||
|             } | ||||
|             var timestamp = timeFormatter.parse(datum); | ||||
|             var bounds = openmct.time.bounds(); | ||||
|             if (timestamp >= bounds.start && timestamp <= bounds.end) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function updateClock(clock) { | ||||
|             // We restrict to bounds if there is no clock (aka fixed mode) to | ||||
|             // prevent users from seeing data they don't expect. | ||||
|             restrictToBounds = !clock; | ||||
|         } | ||||
|  | ||||
|         function callbackIfLatest(datum) { | ||||
|             if (!active) { | ||||
|                 return; // prevent LAD notify after unsubscribe. | ||||
|             } | ||||
|             // If we don't have latest data, store datum for later processing. | ||||
|             if (typeof latestDatum === 'undefined') { | ||||
|                 if (typeof pendingRealtimeDatum === 'undefined' || ( | ||||
|                         isLater(datum, pendingRealtimeDatum) && | ||||
|                         withinBounds(datum) | ||||
|                     )) { | ||||
|  | ||||
|                     pendingRealtimeDatum = datum; | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|             // If there is no latest data, or datum is latest, then notify | ||||
|             // subscriber. | ||||
|             if (latestDatum === false || isLater(datum, latestDatum)) { | ||||
|                 latestDatum = datum; | ||||
|                 applyBoundsFilter(datum); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function updateTimeSystem(timeSystem) { | ||||
|             // Reset subscription state, request new latest data and wait for | ||||
|             // response before filtering lad data. | ||||
|             latestDatum = undefined; | ||||
|             pendingRealtimeDatum = undefined; | ||||
|             timeFormatter = formatters[timeSystem.key]; | ||||
|  | ||||
|             currentRequest++; | ||||
|             var thisRequest = currentRequest; | ||||
|             telemetryAPI.request(domainObject, {strategy: 'latest', size: 1}) | ||||
|                 .then(function (results) { | ||||
|                     return results[results.length - 1]; | ||||
|                 }, function (error) { | ||||
|                     return undefined; | ||||
|                 }) | ||||
|                 .then(function (datum) { | ||||
|                     if (currentRequest !== thisRequest) { | ||||
|                         return; // prevent race. | ||||
|                     } | ||||
|                     if (!datum || !withinBounds(datum)) { | ||||
|                         latestDatum = false; | ||||
|                     } else { | ||||
|                         latestDatum = datum; | ||||
|                         applyBoundsFilter(datum); | ||||
|                     } | ||||
|                     if (pendingRealtimeDatum) { | ||||
|                         callbackIfLatest(pendingRealtimeDatum); | ||||
|                         pendingRealtimeDatum = undefined; | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|  | ||||
|         function reloadInFixedMode(bounds, isTick) { | ||||
|             if (isTick || openmct.time.clock()) { | ||||
|                 return; | ||||
|             } | ||||
|             updateTimeSystem(openmct.time.timeSystem()); | ||||
|         } | ||||
|  | ||||
|         openmct.time.on('clock', updateClock); | ||||
|         openmct.time.on('timeSystem', updateTimeSystem); | ||||
|         openmct.time.on('bounds', reloadInFixedMode); | ||||
|         var internalUnsubscribe = telemetryAPI.subscribe(domainObject, callbackIfLatest); | ||||
|  | ||||
|         updateClock(openmct.time.clock()); | ||||
|         updateTimeSystem(openmct.time.timeSystem()); | ||||
|  | ||||
|         return function unsubscribe() { | ||||
|             active = false; | ||||
|             internalUnsubscribe(); | ||||
|             openmct.time.off('clock', updateClock); | ||||
|             openmct.time.off('timeSystem', updateTimeSystem); | ||||
|             openmct.time.off('bounds', reloadInFixedMode); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|  | ||||
|  | ||||
|     return latestValueSubscription | ||||
| }); | ||||
							
								
								
									
										670
									
								
								src/api/telemetry/latestValueSubscriptionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										670
									
								
								src/api/telemetry/latestValueSubscriptionSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,670 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open openmct 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 openmct 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([ | ||||
|     './latestValueSubscription' | ||||
| ], function ( | ||||
|     latestValueSubscription | ||||
| ) { | ||||
|  | ||||
|     function slowPromise() { | ||||
|         // Emprically, using setTimeout to resolve a promise results in a | ||||
|         // promise that will resolve after every other promise.  This is a | ||||
|         // simple way to defer code. | ||||
|         return new Promise(function (resolve, reject) { | ||||
|             setTimeout(resolve); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     describe("latestValueSubscription", function () { | ||||
|  | ||||
|         var openmct; | ||||
|         var telemetryAPI; | ||||
|         var telemetryMetadata; | ||||
|         var formatMap; | ||||
|         var pendingRequests; | ||||
|         var subscriptions; | ||||
|         var domainObject; | ||||
|         var callback; | ||||
|         var unsubscribe; | ||||
|         var triggerTimeEvent; | ||||
|  | ||||
|         var noLADTestcases = [ | ||||
|             { | ||||
|                 name: "empty LAD", | ||||
|                 trigger: function () { | ||||
|                     pendingRequests[0].resolve([]); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 name: "rejected LAD", | ||||
|                 trigger: function () { | ||||
|                     pendingRequests[0].reject(); | ||||
|                 } | ||||
|             } | ||||
|         ]; | ||||
|  | ||||
|         var outOfBoundsLADTestcases = [ | ||||
|             { | ||||
|                 name: "future LAD", | ||||
|                 trigger: function () { | ||||
|                     pendingRequests[0].resolve([{test: 1123}]); | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|                 name: "prehistoric LAD", | ||||
|                 trigger: function () { | ||||
|                     pendingRequests[0].resolve([{test: -150}]); | ||||
|                 } | ||||
|             } | ||||
|         ]; | ||||
|  | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             openmct = { | ||||
|                 time: jasmine.createSpyObj('timeAPI', [ | ||||
|                     'clock', | ||||
|                     'timeSystem', | ||||
|                     'bounds', | ||||
|                     'on', | ||||
|                     'off' | ||||
|                 ]) | ||||
|             }; | ||||
|  | ||||
|             openmct.time.timeSystem.and.returnValue({key: 'test'}); | ||||
|  | ||||
|             telemetryAPI = jasmine.createSpyObj('telemetryAPI', [ | ||||
|                 'getMetadata', | ||||
|                 'getFormatMap', | ||||
|                 'request', | ||||
|                 'subscribe' | ||||
|             ]); | ||||
|  | ||||
|             telemetryMetadata = jasmine.createSpyObj('metadata', [ | ||||
|                 'values' | ||||
|             ]); | ||||
|             telemetryAPI.getMetadata.and.returnValue(telemetryMetadata); | ||||
|  | ||||
|             formatMap = { | ||||
|                 test: jasmine.createSpyObj('testFormatter', ['parse']), | ||||
|                 other: jasmine.createSpyObj('otherFormatter', ['parse']) | ||||
|             }; | ||||
|             formatMap.test.parse.and.callFake(function (datum) { | ||||
|                 return datum.test; | ||||
|             }); | ||||
|             formatMap.other.parse.and.callFake(function (datum) { | ||||
|                 return datum.other; | ||||
|             }); | ||||
|             telemetryAPI.getFormatMap.and.returnValue(formatMap); | ||||
|  | ||||
|             pendingRequests = []; | ||||
|             telemetryAPI.request.and.callFake(function (domainObject, options) { | ||||
|                 var request = { | ||||
|                     domainObject: domainObject, | ||||
|                     options: options | ||||
|                 }; | ||||
|                 request.promise = new Promise(function (resolve, reject) { | ||||
|                     request.resolve = resolve; | ||||
|                     request.reject = reject; | ||||
|                 }); | ||||
|                 pendingRequests.push(request); | ||||
|                 return request.promise; | ||||
|             }); | ||||
|  | ||||
|             subscriptions = []; | ||||
|             telemetryAPI.subscribe.and.callFake(function (domainObject, callback) { | ||||
|                 var subscription = { | ||||
|                     domainObject: domainObject, | ||||
|                     callback: callback, | ||||
|                     unsubscribe: jasmine.createSpy('unsubscribe') | ||||
|                 }; | ||||
|                 subscriptions.push(subscription); | ||||
|                 return subscription.unsubscribe; | ||||
|             }); | ||||
|  | ||||
|             callback = jasmine.createSpy('callback'); | ||||
|  | ||||
|             domainObject = {}; | ||||
|  | ||||
|             triggerTimeEvent = function (event, args) { | ||||
|                 openmct.time.on.calls.allArgs().filter(function (callArgs) { | ||||
|                     return callArgs[0] === event; | ||||
|                 })[0][1].apply(null, args); | ||||
|             }; | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         // A simple test case to make sure we have appropriate mocks. | ||||
|         it("requests, subscribes, and unsubscribes", function () { | ||||
|             var unsubscribe = latestValueSubscription( | ||||
|                 domainObject, | ||||
|                 callback, | ||||
|                 telemetryAPI, | ||||
|                 openmct | ||||
|             ); | ||||
|  | ||||
|             expect(unsubscribe).toEqual(jasmine.any(Function)); | ||||
|             expect(telemetryAPI.request) | ||||
|                 .toHaveBeenCalledWith(domainObject, jasmine.any(Object)) | ||||
|             expect(telemetryAPI.subscribe) | ||||
|                 .toHaveBeenCalledWith(domainObject, jasmine.any(Function)) | ||||
|             expect(subscriptions[0].unsubscribe).not.toHaveBeenCalled(); | ||||
|  | ||||
|             unsubscribe(); | ||||
|  | ||||
|             expect(subscriptions[0].unsubscribe).toHaveBeenCalled(); | ||||
|         }); | ||||
|  | ||||
|         /** TODO: | ||||
|          * test lad response inside bounds, outside bounds, no response. | ||||
|          * test realtime should wait until lad response (all cases); | ||||
|          * realtime should only notify if later than latest (or no latest). | ||||
|          * | ||||
|          * timesystem change should clear and re-request LAD. | ||||
|          * clock change should enable/disable bounds filtering. | ||||
|          * non-tick bounds change should clear and | ||||
|          * | ||||
|          * | ||||
|          * should receive lad response | ||||
|          * should receive realtime if later than lad. | ||||
|          * should receive lad response (unless outside) | ||||
|          * subscriptions should wait for lad response | ||||
|          * | ||||
|         */ | ||||
|         describe("no clock (AKA fixed)", function () { | ||||
|             var unsubscribe; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 openmct.time.clock.and.returnValue(undefined); | ||||
|                 openmct.time.timeSystem.and.returnValue({key: 'test'}); | ||||
|                 openmct.time.bounds.and.returnValue({start: 0, end: 1000}); | ||||
|                 unsubscribe = latestValueSubscription( | ||||
|                     domainObject, | ||||
|                     callback, | ||||
|                     telemetryAPI, | ||||
|                     openmct | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             describe("nominal LAD response", function () { | ||||
|                 it("provides LAD datum on resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("sends realtime values synchronously after resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         subscriptions[0].callback({test: 456}) | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("holds realtime values until resolved", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}) | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("only sends latest realtime value after resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}); | ||||
|                     subscriptions[0].callback({test: 567}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 567}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("filters realtime values before latest", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 456}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         subscriptions[0].callback({test: 123}) | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("filters realtime values outside bounds", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 456}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         subscriptions[0].callback({test: 1123}) | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 1123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("doesn't override pending value with one outside bounds", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}); | ||||
|                     subscriptions[0].callback({test: 1123}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 1123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("doesn't send out of order realtime value", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         subscriptions[0].callback({test: 567}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|             outOfBoundsLADTestcases.concat(noLADTestcases).forEach(function (testCase) { | ||||
|                 describe(testCase.name, function () { | ||||
|                     it("does not provide LAD datum", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).not.toHaveBeenCalled(); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("sends realtime values synchronously after resolve", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 456}) | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("holds realtime values until resolved", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}) | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("only sends latest realtime value after resolve", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         subscriptions[0].callback({test: 567}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 567}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("filters realtime values outside bounds", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 1123}) | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 1123}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("doesn't override pending value with one outside bounds", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         subscriptions[0].callback({test: 1123}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 1123}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("doesn't send out of order realtime value", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 567}); | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                             subscriptions[0].callback({test: 456}); | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("with clock (AKA realtime)", function () { | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 openmct.time.clock.and.returnValue({}); | ||||
|                 openmct.time.timeSystem.and.returnValue({key: 'test'}); | ||||
|                 openmct.time.bounds.and.returnValue({start: 0, end: 1000}); | ||||
|                 unsubscribe = latestValueSubscription( | ||||
|                     domainObject, | ||||
|                     callback, | ||||
|                     telemetryAPI, | ||||
|                     openmct | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             describe("nominal LAD response", function () { | ||||
|                 it("provides LAD datum on resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("sends realtime values synchronously after resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         subscriptions[0].callback({test: 456}) | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("holds realtime values until resolved", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}) | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("only sends latest realtime value after resolve", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}); | ||||
|                     subscriptions[0].callback({test: 567}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 567}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("filters realtime values before latest", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 456}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         subscriptions[0].callback({test: 123}) | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("does not filter realtime values outside bounds", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 456}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                         subscriptions[0].callback({test: 1123}) | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 1123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("overrides pending realtime value with one outside bounds", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     subscriptions[0].callback({test: 456}); | ||||
|                     subscriptions[0].callback({test: 1123}); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 1123}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("doesn't send out of order realtime value", function (done) { | ||||
|                     pendingRequests[0].resolve([{test: 123}]); | ||||
|                     slowPromise().then(function () { | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 123}); | ||||
|                         subscriptions[0].callback({test: 567}); | ||||
|                         expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         done(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             noLADTestcases.forEach(function (testCase) { | ||||
|                 describe(testCase.name, function () { | ||||
|                     it("does not provide LAD datum", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).not.toHaveBeenCalled(); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("sends realtime values synchronously after resolve", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 456}) | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("holds realtime values until resolved", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}) | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("only sends latest realtime value after resolve", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         subscriptions[0].callback({test: 567}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                         expect(callback).not.toHaveBeenCalledWith({test: 567}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("doesn't filter realtime values outside bounds", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 1123}) | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 1123}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("doesn't filter pending realtime values outside bounds", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         subscriptions[0].callback({test: 456}); | ||||
|                         subscriptions[0].callback({test: 1123}); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 1123}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|  | ||||
|                     it("doesn't send out of order realtime value", function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             subscriptions[0].callback({test: 567}); | ||||
|                             expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                             subscriptions[0].callback({test: 456}); | ||||
|                             expect(callback).not.toHaveBeenCalledWith({test: 456}); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("out of bounds LAD", function () { | ||||
|                 outOfBoundsLADTestcases.forEach(function (testCase) { | ||||
|                     it(`provides ${testCase} datum`, function (done) { | ||||
|                         testCase.trigger(); | ||||
|                         slowPromise().then(function () { | ||||
|                             expect(callback).toHaveBeenCalled(); | ||||
|                             done(); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("clock changes", function () { | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 openmct.time.timeSystem.and.returnValue({key: 'test'}); | ||||
|                 openmct.time.bounds.and.returnValue({start: 0, end: 1000}); | ||||
|             }); | ||||
|  | ||||
|             it("starts bounds filtering when clock is cleared", function (done) { | ||||
|                 openmct.time.clock.and.returnValue({}); | ||||
|                 unsubscribe = latestValueSubscription( | ||||
|                     domainObject, | ||||
|                     callback, | ||||
|                     telemetryAPI, | ||||
|                     openmct | ||||
|                 ); | ||||
|                 pendingRequests[0].resolve([]); | ||||
|                 slowPromise().then(function () { | ||||
|                     subscriptions[0].callback({test: 1123}); | ||||
|                     expect(callback).toHaveBeenCalledWith({test: 1123}); | ||||
|                     triggerTimeEvent('clock', undefined); | ||||
|                     subscriptions[0].callback({test: 1223}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 1223}); | ||||
|                     done(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it("stops bounds filtering when clock is set", function (done) { | ||||
|                 openmct.time.clock.and.returnValue(undefined); | ||||
|                 unsubscribe = latestValueSubscription( | ||||
|                     domainObject, | ||||
|                     callback, | ||||
|                     telemetryAPI, | ||||
|                     openmct | ||||
|                 ); | ||||
|                 pendingRequests[0].resolve([]); | ||||
|                 slowPromise().then(function () { | ||||
|                     subscriptions[0].callback({test: 1123}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 1123}); | ||||
|                     triggerTimeEvent('clock', [{}]); | ||||
|                     subscriptions[0].callback({test: 1223}); | ||||
|                     expect(callback).toHaveBeenCalledWith({test: 1223}); | ||||
|                     done(); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("timesystem changes", function () { | ||||
|             it("requeries lad and uses new keys.", function (done) { | ||||
|                 openmct.time.clock.and.returnValue(undefined); | ||||
|                 openmct.time.timeSystem.and.returnValue({key: 'test'}); | ||||
|                 openmct.time.bounds.and.returnValue({start: 0, end: 1000}); | ||||
|                 unsubscribe = latestValueSubscription( | ||||
|                     domainObject, | ||||
|                     callback, | ||||
|                     telemetryAPI, | ||||
|                     openmct | ||||
|                 ); | ||||
|  | ||||
|                 expect(pendingRequests.length).toBe(1); | ||||
|                 expect(subscriptions.length).toBe(1); | ||||
|                 pendingRequests[0].resolve([{test: 234}]); | ||||
|                 slowPromise().then(function () { | ||||
|                     expect(callback).toHaveBeenCalledWith({test: 234}); | ||||
|                     triggerTimeEvent('timeSystem', [{key: 'other'}]); | ||||
|                     expect(pendingRequests.length).toBe(2); | ||||
|                     expect(subscriptions.length).toBe(1); | ||||
|                     pendingRequests[1].resolve([{test: 123, other: 456}]); | ||||
|                     return slowPromise(); // wait for new lad to resolve. | ||||
|                 }).then(function() { | ||||
|                     expect(callback).toHaveBeenCalledWith({test: 123, other: 456}); | ||||
|                     // should have synchronous callbacks when other is greater. | ||||
|                     subscriptions[0].callback({test: 234, other: 567}); | ||||
|                     expect(callback).toHaveBeenCalledWith({test: 234, other:567}); | ||||
|                     // should filter out when other is less. | ||||
|                     subscriptions[0].callback({test: 345, other: 345}); | ||||
|                     expect(callback).not.toHaveBeenCalledWith({test: 345, other: 345}); | ||||
|                     done(); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("does not filter when no value matches timesystem", function (done) { | ||||
|             openmct.time.clock.and.returnValue(undefined); | ||||
|             openmct.time.timeSystem.and.returnValue({key: 'blah'}); | ||||
|             openmct.time.bounds.and.returnValue({start: 0, end: 1000}); | ||||
|             unsubscribe = latestValueSubscription( | ||||
|                 domainObject, | ||||
|                 callback, | ||||
|                 telemetryAPI, | ||||
|                 openmct | ||||
|             ); | ||||
|             pendingRequests[0].resolve([{test: 1234}]); | ||||
|             slowPromise().then(function () { | ||||
|                 expect(callback).toHaveBeenCalledWith({test: 1234}); | ||||
|                 subscriptions[0].callback({test: 567}); | ||||
|                 expect(callback).toHaveBeenCalledWith({test: 567}); | ||||
|                 done(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         describe("on bounds event", function () { | ||||
|             // TODO: test cases for what happens when bounds changes. | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
| @@ -41,8 +41,7 @@ define([ | ||||
|             spyOn(mockmct.telemetry, 'getMetadata'); | ||||
|             spyOn(mockmct.telemetry, 'getValueFormatter'); | ||||
|             spyOn(mockmct.telemetry, 'limitEvaluator'); | ||||
|             spyOn(mockmct.telemetry, 'request'); | ||||
|             spyOn(mockmct.telemetry, 'subscribe'); | ||||
|             spyOn(mockmct.telemetry, 'latest'); | ||||
|  | ||||
|             var plugin = new AutoflowTabularPlugin({ type: testType }); | ||||
|             plugin(mockmct); | ||||
| @@ -140,15 +139,11 @@ define([ | ||||
|                         return mockFormatter; | ||||
|                     }); | ||||
|                     mockmct.telemetry.limitEvaluator.and.returnValue(mockEvaluator); | ||||
|                     mockmct.telemetry.subscribe.and.callFake(function (obj, callback) { | ||||
|                     mockmct.telemetry.latest.and.callFake(function (obj, callback) { | ||||
|                         var key = obj.identifier.key; | ||||
|                         callbacks[key] = callback; | ||||
|                         return mockUnsubscribes[key]; | ||||
|                     }); | ||||
|                     mockmct.telemetry.request.and.callFake(function (obj, request) { | ||||
|                         var key = obj.identifier.key; | ||||
|                         return Promise.resolve([testHistories[key]]); | ||||
|                     }); | ||||
|                     mockMetadata.valuesForHints.and.callFake(function (hints) { | ||||
|                         return [{ hint: hints[0] }]; | ||||
|                     }); | ||||
| @@ -232,19 +227,6 @@ define([ | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("displays historical telemetry", function () { | ||||
|                     function rowTextDefined() { | ||||
|                         return $(testContainer).find(".l-autoflow-item").filter(".r").text() !== ""; | ||||
|                     } | ||||
|                     return domObserver.when(rowTextDefined).then(function () { | ||||
|                         testKeys.forEach(function (key, index) { | ||||
|                             var datum = testHistories[key]; | ||||
|                             var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r"); | ||||
|                             expect($cell.text()).toEqual(String(datum.range)); | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("displays incoming telemetry", function () { | ||||
|                     var testData = testKeys.map(function (key, index) { | ||||
|                         return { key: key, range: index * 100, domain: key + index }; | ||||
|   | ||||
| @@ -66,19 +66,10 @@ define([], function () { | ||||
|      * Activate this controller; begin listening for changes. | ||||
|      */ | ||||
|     AutoflowTabularRowController.prototype.activate = function () { | ||||
|         this.unsubscribe = this.openmct.telemetry.subscribe( | ||||
|         this.unsubscribe = this.openmct.telemetry.latest( | ||||
|             this.domainObject, | ||||
|             this.updateRowData.bind(this) | ||||
|         ); | ||||
|  | ||||
|         this.openmct.telemetry.request( | ||||
|             this.domainObject, | ||||
|             { size: 1 } | ||||
|         ).then(function (history) { | ||||
|             if (!this.initialized && history.length > 0) { | ||||
|                 this.updateRowData(history[history.length - 1]); | ||||
|             } | ||||
|         }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user