mirror of
https://github.com/nasa/openmct.git
synced 2025-04-09 04:14:32 +00:00
Merge remote-tracking branch 'origin/open614d' into open-master
This commit is contained in:
commit
9981543156
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
*.tgz
|
||||
*.DS_Store
|
||||
*.idea
|
||||
*.sass-cache
|
||||
|
||||
# External dependencies
|
||||
|
||||
|
@ -13,6 +13,7 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function SinewaveTelemetryProvider($q, $timeout) {
|
||||
var subscriptions = [];
|
||||
|
||||
//
|
||||
function matchesSource(request) {
|
||||
@ -43,8 +44,48 @@ define(
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function handleSubscriptions() {
|
||||
subscriptions.forEach(function (subscription) {
|
||||
var requests = subscription.requests;
|
||||
subscription.callback(doPackage(
|
||||
requests.filter(matchesSource).map(generateData)
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
function startGenerating() {
|
||||
$timeout(function () {
|
||||
handleSubscriptions();
|
||||
if (subscriptions.length > 0) {
|
||||
startGenerating();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function subscribe(callback, requests) {
|
||||
var subscription = {
|
||||
callback: callback,
|
||||
requests: requests
|
||||
};
|
||||
|
||||
function unsubscribe() {
|
||||
subscriptions = subscriptions.filter(function (s) {
|
||||
return s !== subscription;
|
||||
});
|
||||
}
|
||||
|
||||
subscriptions.push(subscription);
|
||||
|
||||
if (subscriptions.length === 1) {
|
||||
startGenerating();
|
||||
}
|
||||
|
||||
return unsubscribe;
|
||||
}
|
||||
|
||||
return {
|
||||
requestTelemetry: requestTelemetry
|
||||
requestTelemetry: requestTelemetry,
|
||||
subscribe: subscribe
|
||||
};
|
||||
}
|
||||
|
||||
|
29
example/taxonomy/bundle.json
Normal file
29
example/taxonomy/bundle.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Example taxonomy",
|
||||
"description": "Example illustrating the addition of a static top-level hierarchy",
|
||||
"extensions": {
|
||||
"roots": [
|
||||
{
|
||||
"id": "exampleTaxonomy",
|
||||
"model": {
|
||||
"type": "folder",
|
||||
"name": "Stub Subsystems",
|
||||
"composition": [
|
||||
"examplePacket0",
|
||||
"examplePacket1",
|
||||
"examplePacket2"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"provides": "modelService",
|
||||
"type": "provider",
|
||||
"implementation": "ExampleTaxonomyModelProvider.js",
|
||||
"depends": [ "$q" ]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
48
example/taxonomy/src/ExampleTaxonomyModelProvider.js
Normal file
48
example/taxonomy/src/ExampleTaxonomyModelProvider.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
function ExampleTaxonomyModelProvider($q) {
|
||||
var models = {},
|
||||
packetId,
|
||||
telemetryId,
|
||||
i,
|
||||
j;
|
||||
|
||||
// Add some "subsystems"
|
||||
for (i = 0; i < 3; i += 1) {
|
||||
packetId = "examplePacket" + i;
|
||||
|
||||
models[packetId] = {
|
||||
name: "Stub Subsystem " + (i + 1),
|
||||
type: "telemetry.panel",
|
||||
composition: []
|
||||
};
|
||||
|
||||
// Add some "telemetry points"
|
||||
for (j = 0; j < 100 * (i + 1); j += 1) {
|
||||
telemetryId = "exampleTelemetry" + j;
|
||||
models[telemetryId] = {
|
||||
name: "SWG" + i + "." + j,
|
||||
type: "generator",
|
||||
telemetry: {
|
||||
period: 10 + i + j
|
||||
}
|
||||
};
|
||||
models[packetId].composition.push(telemetryId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getModels: function () {
|
||||
return $q.when(models);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return ExampleTaxonomyModelProvider;
|
||||
}
|
||||
);
|
@ -24,49 +24,59 @@
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TreeNodeController",
|
||||
"implementation": "TreeNodeController.js",
|
||||
"implementation": "controllers/TreeNodeController.js",
|
||||
"depends": [ "$scope", "$timeout" ]
|
||||
},
|
||||
{
|
||||
"key": "ActionGroupController",
|
||||
"implementation": "ActionGroupController.js",
|
||||
"implementation": "controllers/ActionGroupController.js",
|
||||
"depends": [ "$scope" ]
|
||||
},
|
||||
{
|
||||
"key": "ToggleController",
|
||||
"implementation": "ToggleController.js"
|
||||
"implementation": "controllers/ToggleController.js"
|
||||
},
|
||||
{
|
||||
"key": "ContextMenuController",
|
||||
"implementation": "ContextMenuController.js",
|
||||
"implementation": "controllers/ContextMenuController.js",
|
||||
"depends": [ "$scope" ]
|
||||
},
|
||||
{
|
||||
"key": "ClickAwayController",
|
||||
"implementation": "ClickAwayController.js",
|
||||
"implementation": "controllers/ClickAwayController.js",
|
||||
"depends": [ "$scope", "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "ViewSwitcherController",
|
||||
"implementation": "ViewSwitcherController.js",
|
||||
"implementation": "controllers/ViewSwitcherController.js",
|
||||
"depends": [ "$scope" ]
|
||||
},
|
||||
{
|
||||
"key": "BottomBarController",
|
||||
"implementation": "BottomBarController.js",
|
||||
"implementation": "controllers/BottomBarController.js",
|
||||
"depends": [ "indicators[]" ]
|
||||
},
|
||||
{
|
||||
"key": "GetterSetterController",
|
||||
"implementation": "controllers/GetterSetterController.js",
|
||||
"depends": [ "$scope" ]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctContainer",
|
||||
"implementation": "MCTContainer.js",
|
||||
"implementation": "directives/MCTContainer.js",
|
||||
"depends": [ "containers[]" ]
|
||||
},
|
||||
{
|
||||
"key": "mctDrag",
|
||||
"implementation": "MCTDrag.js",
|
||||
"implementation": "directives/MCTDrag.js",
|
||||
"depends": [ "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "mctResize",
|
||||
"implementation": "directives/MCTResize.js",
|
||||
"depends": [ "$timeout" ]
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
|
Binary file not shown.
@ -1,5 +1,13 @@
|
||||
<!-- look at action-button for example -->
|
||||
<span class="t-filter l-filter">
|
||||
<input type="search" class="t-filter-input" ng-model="filter" placeholder="Filter..."/>
|
||||
<a class="ui-symbol t-a-clear s-a-clear" ng-click="filter = null">x</a>
|
||||
<span class="t-filter l-filter"
|
||||
ng-controller="GetterSetterController">
|
||||
<input type="search"
|
||||
class="t-filter-input"
|
||||
ng-model="getterSetter.value"
|
||||
placeholder="Filter..."/>
|
||||
<a class="ui-symbol t-a-clear s-a-clear"
|
||||
ng-show="getterSetter.value !== ''"
|
||||
ng-click="getterSetter.value = ''">
|
||||
x
|
||||
</a>
|
||||
</span>
|
@ -0,0 +1,69 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This controller acts as an adapter to permit getter-setter
|
||||
* functions to be used as ng-model arguments to controls,
|
||||
* such as the input-filter. This is supported natively in
|
||||
* Angular 1.3+ via `ng-model-options`, so this controller
|
||||
* should be made obsolete after any upgrade to Angular 1.3.
|
||||
*
|
||||
* It expects to find in scope a value `ngModel` which is a
|
||||
* function which, when called with no arguments, acts as a
|
||||
* getter, and when called with one argument, acts as a setter.
|
||||
*
|
||||
* It also publishes into the scope a value `getterSetter.value`
|
||||
* which is meant to be used as an assignable expression.
|
||||
*
|
||||
* This controller watches both of these; when one changes,
|
||||
* it will update the other's value to match. Because of this,
|
||||
* the `ngModel` function should be both stable and computationally
|
||||
* inexpensive, as it will be invoked often.
|
||||
*
|
||||
* Getter-setter style models can be preferable when there
|
||||
* is significant indirection between templates; "dotless"
|
||||
* expressions in `ng-model` can behave unexpectedly due to the
|
||||
* rules of scope, but dots are lost when passed in via `ng-model`
|
||||
* (so if a control is internally implemented using regular
|
||||
* form elements, it can't transparently pass through the `ng-model`
|
||||
* parameter it received.) Getter-setter functions are never the
|
||||
* target of a scope assignment and so avoid this problem.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's scope
|
||||
*/
|
||||
function GetterSetterController($scope) {
|
||||
|
||||
// Update internal assignable state based on changes
|
||||
// to the getter-setter function.
|
||||
function updateGetterSetter() {
|
||||
if (typeof $scope.ngModel === 'function') {
|
||||
$scope.getterSetter.value = $scope.ngModel();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the external getter-setter based on changes
|
||||
// to the assignable state.
|
||||
function updateNgModel() {
|
||||
if (typeof $scope.ngModel === 'function') {
|
||||
$scope.ngModel($scope.getterSetter.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for changes to both expressions
|
||||
$scope.$watch("ngModel()", updateGetterSetter);
|
||||
$scope.$watch("getterSetter.value", updateNgModel);
|
||||
|
||||
// Publish an assignable field into scope.
|
||||
$scope.getterSetter = {};
|
||||
|
||||
}
|
||||
|
||||
return GetterSetterController;
|
||||
|
||||
}
|
||||
);
|
82
platform/commonUI/general/src/directives/MCTResize.js
Normal file
82
platform/commonUI/general/src/directives/MCTResize.js
Normal file
@ -0,0 +1,82 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
// Default resize interval
|
||||
var DEFAULT_INTERVAL = 100;
|
||||
|
||||
/**
|
||||
* The mct-resize directive allows the size of a displayed
|
||||
* HTML element to be tracked. This is done by polling,
|
||||
* since the DOM API does not currently provide suitable
|
||||
* events to watch this reliably.
|
||||
*
|
||||
* Attributes related to this directive are interpreted as
|
||||
* follows:
|
||||
*
|
||||
* * `mct-resize`: An Angular expression to evaluate when
|
||||
* the size changes; the variable `bounds` will be provided
|
||||
* with two fields, `width` and `height`, both in pixels.
|
||||
* * `mct-resize-interval`: Optional; the interval, in milliseconds,
|
||||
* at which to watch for updates. In some cases checking for
|
||||
* resize can carry a cost (it forces recalculation of
|
||||
* positions within the document) so it may be preferable to watch
|
||||
* infrequently. If omitted, a default of 100ms will be used.
|
||||
* This is an Angular expression, and it will be re-evaluated after
|
||||
* each interval.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
*/
|
||||
function MCTResize($timeout) {
|
||||
|
||||
// Link; start listening for changes to an element's size
|
||||
function link(scope, element, attrs) {
|
||||
var lastBounds;
|
||||
|
||||
// Determine how long to wait before the next update
|
||||
function currentInterval() {
|
||||
return attrs.mctResizeInterval ?
|
||||
scope.$eval(attrs.mctResizeInterval) :
|
||||
DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
// Evaluate mct-resize with the current bounds
|
||||
function fireEval(bounds) {
|
||||
// Only update when bounds actually change
|
||||
if (!lastBounds ||
|
||||
lastBounds.width !== bounds.width ||
|
||||
lastBounds.height !== bounds.height) {
|
||||
scope.$eval(attrs.mctResize, { bounds: bounds });
|
||||
lastBounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback to fire after each timeout;
|
||||
// update bounds and schedule another timeout
|
||||
function onInterval() {
|
||||
fireEval({
|
||||
width: element[0].offsetWidth,
|
||||
height: element[0].offsetHeight
|
||||
});
|
||||
$timeout(onInterval, currentInterval());
|
||||
}
|
||||
|
||||
// Handle the initial callback
|
||||
onInterval();
|
||||
}
|
||||
|
||||
return {
|
||||
// mct-resize only makes sense as an attribute
|
||||
restrict: "A",
|
||||
// Link function, to begin watching for changes
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTResize;
|
||||
}
|
||||
);
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ActionGroupController"],
|
||||
["../../src/controllers/ActionGroupController"],
|
||||
function (ActionGroupController) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/BottomBarController"],
|
||||
["../../src/controllers/BottomBarController"],
|
||||
function (BottomBarController) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ClickAwayController"],
|
||||
["../../src/controllers/ClickAwayController"],
|
||||
function (ClickAwayController) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ContextMenuController"],
|
||||
["../../src/controllers/ContextMenuController"],
|
||||
function (ContextMenuController) {
|
||||
"use strict";
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/GetterSetterController"],
|
||||
function (GetterSetterController) {
|
||||
"use strict";
|
||||
|
||||
describe("The getter-setter controller", function () {
|
||||
var mockScope,
|
||||
mockModel,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
||||
mockModel = jasmine.createSpy("ngModel");
|
||||
mockScope.ngModel = mockModel;
|
||||
controller = new GetterSetterController(mockScope);
|
||||
});
|
||||
|
||||
it("watches for changes to external and internal mode", function () {
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"ngModel()",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||
"getterSetter.value",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("updates an external function when changes are detected", function () {
|
||||
mockScope.getterSetter.value = "some new value";
|
||||
// Verify precondition
|
||||
expect(mockScope.ngModel)
|
||||
.not.toHaveBeenCalledWith("some new value");
|
||||
// Fire the matching watcher
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === "getterSetter.value") {
|
||||
call.args[1](mockScope.getterSetter.value);
|
||||
}
|
||||
});
|
||||
// Verify getter-setter was notified
|
||||
expect(mockScope.ngModel)
|
||||
.toHaveBeenCalledWith("some new value");
|
||||
});
|
||||
|
||||
it("updates internal state when external changes are detected", function () {
|
||||
mockScope.ngModel.andReturn("some other new value");
|
||||
// Verify precondition
|
||||
expect(mockScope.getterSetter.value).toBeUndefined();
|
||||
// Fire the matching watcher
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === "ngModel()") {
|
||||
call.args[1]("some other new value");
|
||||
}
|
||||
});
|
||||
// Verify state in scope was updated
|
||||
expect(mockScope.getterSetter.value)
|
||||
.toEqual("some other new value");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/ToggleController"],
|
||||
["../../src/controllers/ToggleController"],
|
||||
function (ToggleController) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TreeNodeController"],
|
||||
["../../src/controllers/TreeNodeController"],
|
||||
function (TreeNodeController) {
|
||||
"use strict";
|
||||
|
@ -4,7 +4,7 @@
|
||||
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../src/ViewSwitcherController"],
|
||||
["../../src/controllers/ViewSwitcherController"],
|
||||
function (ViewSwitcherController) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/MCTContainer"],
|
||||
["../../src/directives/MCTContainer"],
|
||||
function (MCTContainer) {
|
||||
"use strict";
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/MCTDrag"],
|
||||
["../../src/directives/MCTDrag"],
|
||||
function (MCTDrag) {
|
||||
"use strict";
|
||||
|
68
platform/commonUI/general/test/directives/MCTResizeSpec.js
Normal file
68
platform/commonUI/general/test/directives/MCTResizeSpec.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/directives/MCTResize"],
|
||||
function (MCTResize) {
|
||||
"use strict";
|
||||
|
||||
describe("The mct-resize directive", function () {
|
||||
var mockTimeout,
|
||||
mockScope,
|
||||
testElement,
|
||||
testAttrs,
|
||||
mctResize;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockScope = jasmine.createSpyObj("$scope", ["$eval"]);
|
||||
|
||||
testElement = { offsetWidth: 100, offsetHeight: 200 };
|
||||
testAttrs = { mctResize: "some-expr" };
|
||||
|
||||
mctResize = new MCTResize(mockTimeout);
|
||||
});
|
||||
|
||||
it("is applicable as an attribute only", function () {
|
||||
expect(mctResize.restrict).toEqual("A");
|
||||
});
|
||||
|
||||
it("starts tracking size changes upon link", function () {
|
||||
expect(mockTimeout).not.toHaveBeenCalled();
|
||||
mctResize.link(mockScope, [testElement], testAttrs);
|
||||
expect(mockTimeout).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Number)
|
||||
);
|
||||
expect(mockScope.$eval).toHaveBeenCalledWith(
|
||||
testAttrs.mctResize,
|
||||
{ bounds: { width: 100, height: 200 } }
|
||||
);
|
||||
});
|
||||
|
||||
it("reports size changes on a timeout", function () {
|
||||
mctResize.link(mockScope, [testElement], testAttrs);
|
||||
|
||||
// Change the element's apparent size
|
||||
testElement.offsetWidth = 300;
|
||||
testElement.offsetHeight = 350;
|
||||
|
||||
// Shouldn't know about this yet...
|
||||
expect(mockScope.$eval).not.toHaveBeenCalledWith(
|
||||
testAttrs.mctResize,
|
||||
{ bounds: { width: 300, height: 350 } }
|
||||
);
|
||||
|
||||
// Fire the timeout
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
|
||||
// Should have triggered an evaluation of mctResize
|
||||
// with the new width & height
|
||||
expect(mockScope.$eval).toHaveBeenCalledWith(
|
||||
testAttrs.mctResize,
|
||||
{ bounds: { width: 300, height: 350 } }
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,11 +1,13 @@
|
||||
[
|
||||
"ActionGroupController",
|
||||
"BottomBarController",
|
||||
"ClickAwayController",
|
||||
"ContextMenuController",
|
||||
"MCTContainer",
|
||||
"MCTDrag",
|
||||
"ToggleController",
|
||||
"TreeNodeController",
|
||||
"ViewSwitcherController"
|
||||
"controllers/ActionGroupController",
|
||||
"controllers/BottomBarController",
|
||||
"controllers/ClickAwayController",
|
||||
"controllers/ContextMenuController",
|
||||
"controllers/GetterSetterController",
|
||||
"controllers/ToggleController",
|
||||
"controllers/TreeNodeController",
|
||||
"controllers/ViewSwitcherController",
|
||||
"directives/MCTContainer",
|
||||
"directives/MCTDrag",
|
||||
"directives/MCTResize"
|
||||
]
|
@ -28,6 +28,11 @@
|
||||
{
|
||||
"key": "telemetryFormatter",
|
||||
"implementation": "TelemetryFormatter.js"
|
||||
},
|
||||
{
|
||||
"key": "telemetrySubscriber",
|
||||
"implementation": "TelemetrySubscriber.js",
|
||||
"depends": [ "$q", "$timeout" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -38,6 +38,23 @@ define(
|
||||
})).then(mergeResults);
|
||||
}
|
||||
|
||||
// Subscribe to updates from all providers
|
||||
function subscribe(callback, requests) {
|
||||
var unsubscribes = telemetryProviders.map(function (provider) {
|
||||
return provider.subscribe(callback, requests);
|
||||
});
|
||||
|
||||
// Return an unsubscribe function that invokes unsubscribe
|
||||
// for all providers.
|
||||
return function () {
|
||||
unsubscribes.forEach(function (unsubscribe) {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Request telemetry data.
|
||||
@ -47,7 +64,23 @@ define(
|
||||
* which may (or may not, depending on
|
||||
* availability) satisfy the requests
|
||||
*/
|
||||
requestTelemetry: requestTelemetry
|
||||
requestTelemetry: requestTelemetry,
|
||||
/**
|
||||
* Subscribe to streaming updates to telemetry data.
|
||||
* The provided callback will be invoked as new
|
||||
* telemetry becomes available; as an argument, it
|
||||
* will receive an object of key-value pairs, where
|
||||
* keys are source identifiers and values are objects
|
||||
* of key-value pairs, where keys are point identifiers
|
||||
* and values are TelemetrySeries objects containing
|
||||
* the latest streaming telemetry.
|
||||
* @param {Function} callback the callback to invoke
|
||||
* @param {TelemetryRequest[]} requests an array of
|
||||
* requests to be subscribed upon
|
||||
* @returns {Function} a function which can be called
|
||||
* to unsubscribe
|
||||
*/
|
||||
subscribe: subscribe
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,20 +16,22 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryCapability($injector, $q, $log, domainObject) {
|
||||
var telemetryService;
|
||||
var telemetryService,
|
||||
subscriptions = [],
|
||||
unsubscribeFunction;
|
||||
|
||||
// We could depend on telemetryService directly, but
|
||||
// there isn't a platform implementation of this;
|
||||
function getTelemetryService() {
|
||||
if (!telemetryService) {
|
||||
if (telemetryService === undefined) {
|
||||
try {
|
||||
telemetryService =
|
||||
$q.when($injector.get("telemetryService"));
|
||||
$injector.get("telemetryService");
|
||||
} catch (e) {
|
||||
// $injector should throw is telemetryService
|
||||
// $injector should throw if telemetryService
|
||||
// is unavailable or unsatisfiable.
|
||||
$log.warn("Telemetry service unavailable");
|
||||
telemetryService = $q.reject(e);
|
||||
telemetryService = null;
|
||||
}
|
||||
}
|
||||
return telemetryService;
|
||||
@ -83,16 +85,34 @@ define(
|
||||
}
|
||||
|
||||
// Issue a request to the service
|
||||
function requestTelemetryFromService(telemetryService) {
|
||||
function requestTelemetryFromService() {
|
||||
return telemetryService.requestTelemetry([fullRequest]);
|
||||
}
|
||||
|
||||
// If a telemetryService is not available,
|
||||
// getTelemetryService() should reject, and this should
|
||||
// bubble through subsequent then calls.
|
||||
return getTelemetryService()
|
||||
.then(requestTelemetryFromService)
|
||||
.then(getRelevantResponse);
|
||||
return getTelemetryService() &&
|
||||
requestTelemetryFromService()
|
||||
.then(getRelevantResponse);
|
||||
}
|
||||
|
||||
// Listen for real-time and/or streaming updates
|
||||
function subscribe(callback, request) {
|
||||
var fullRequest = buildRequest(request || {});
|
||||
|
||||
// Unpack the relevant telemetry series
|
||||
function update(telemetries) {
|
||||
var source = fullRequest.source,
|
||||
key = fullRequest.key,
|
||||
result = ((telemetries || {})[source] || {})[key];
|
||||
if (result) {
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
return getTelemetryService() &&
|
||||
telemetryService.subscribe(update, [fullRequest]);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -115,7 +135,18 @@ define(
|
||||
// type-level and object-level telemetry
|
||||
// properties
|
||||
return buildRequest({});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to updates to telemetry data for this domain
|
||||
* object.
|
||||
* @param {Function} callback a function to call when new
|
||||
* data becomes available; the telemetry series
|
||||
* containing the data will be given as an argument.
|
||||
* @param {TelemetryRequest} [request] parameters for the
|
||||
* subscription request
|
||||
*/
|
||||
subscribe: subscribe
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,11 @@ define(
|
||||
|
||||
// Used for getTelemetryObjects; a reference is
|
||||
// stored so that this can be called in a watch
|
||||
telemetryObjects: []
|
||||
telemetryObjects: [],
|
||||
|
||||
// Whether or not this controller is active; once
|
||||
// scope is destroyed, polling should stop.
|
||||
active: true
|
||||
};
|
||||
|
||||
// Broadcast that a telemetryUpdate has occurred.
|
||||
@ -227,14 +231,25 @@ define(
|
||||
}
|
||||
|
||||
self.refreshing = false;
|
||||
startTimeout();
|
||||
|
||||
if (self.active) {
|
||||
startTimeout();
|
||||
}
|
||||
}, self.interval);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop polling for changes
|
||||
function deactivate() {
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
// Watch for a represented domain object
|
||||
$scope.$watch("domainObject", getTelemetryObjects);
|
||||
|
||||
// Stop polling when destroyed
|
||||
$scope.$on("$destroy", deactivate);
|
||||
|
||||
// Begin polling for data changes
|
||||
startTimeout();
|
||||
|
||||
|
54
platform/telemetry/src/TelemetrySubscriber.js
Normal file
54
platform/telemetry/src/TelemetrySubscriber.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
["./TelemetrySubscription"],
|
||||
function (TelemetrySubscription) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The TelemetrySubscriber is a service which allows
|
||||
* subscriptions to be made for new data associated with
|
||||
* domain objects. It is exposed as a service named
|
||||
* `telemetrySubscriber`.
|
||||
*
|
||||
* Subscriptions may also be made directly using the
|
||||
* `telemetry` capability of a domain objcet; the subscriber
|
||||
* uses this as well, but additionally handles delegation
|
||||
* (e.g. for telemetry panels) as well as latest-value
|
||||
* extraction.
|
||||
*
|
||||
* @constructor
|
||||
* @param $q Angular's $q
|
||||
* @param $timeout Angular's $timeout
|
||||
*/
|
||||
function TelemetrySubscriber($q, $timeout) {
|
||||
return {
|
||||
/**
|
||||
* Subscribe to streaming telemetry updates
|
||||
* associated with this domain object (either
|
||||
* directly or via capability delegation.)
|
||||
*
|
||||
* @param {DomainObject} domainObject the object whose
|
||||
* associated telemetry data is of interest
|
||||
* @param {Function} callback a function to invoke
|
||||
* when new data has become available.
|
||||
* @returns {TelemetrySubscription} the subscription,
|
||||
* which will provide access to latest values.
|
||||
*
|
||||
* @method
|
||||
* @memberof TelemetrySubscriber
|
||||
*/
|
||||
subscribe: function (domainObject, callback) {
|
||||
return new TelemetrySubscription(
|
||||
$q,
|
||||
$timeout,
|
||||
domainObject,
|
||||
callback
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return TelemetrySubscriber;
|
||||
}
|
||||
);
|
199
platform/telemetry/src/TelemetrySubscription.js
Normal file
199
platform/telemetry/src/TelemetrySubscription.js
Normal file
@ -0,0 +1,199 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* A TelemetrySubscription tracks latest values for streaming
|
||||
* telemetry data and handles notifying interested observers.
|
||||
* It implements the interesting behavior behind the
|
||||
* `telemetrySubscriber` service.
|
||||
*
|
||||
* Subscriptions may also be made directly using the
|
||||
* `telemetry` capability of a domain objcet; the subscriber
|
||||
* uses this as well, but additionally handles delegation
|
||||
* (e.g. for telemetry panels) as well as latest-value
|
||||
* extraction.
|
||||
*
|
||||
* @constructor
|
||||
* @param $q Angular's $q
|
||||
* @param $timeout Angular's $timeout
|
||||
* @param {DomainObject} domainObject the object whose
|
||||
* associated telemetry data is of interest
|
||||
* @param {Function} callback a function to invoke
|
||||
* when new data has become available.
|
||||
*/
|
||||
function TelemetrySubscription($q, $timeout, domainObject, callback) {
|
||||
var unsubscribePromise,
|
||||
latestValues = {},
|
||||
telemetryObjects = [],
|
||||
updatePending;
|
||||
|
||||
// Look up domain objects which have telemetry capabilities.
|
||||
// This will either be the object in view, or object that
|
||||
// this object delegates its telemetry capability to.
|
||||
function promiseRelevantObjects(domainObject) {
|
||||
// If object has been cleared, there are no relevant
|
||||
// telemetry-providing domain objects.
|
||||
if (!domainObject) {
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
// Otherwise, try delegation first, and attach the
|
||||
// object itself if it has a telemetry capability.
|
||||
return $q.when(domainObject.useCapability(
|
||||
"delegation",
|
||||
"telemetry"
|
||||
)).then(function (result) {
|
||||
var head = domainObject.hasCapability("telemetry") ?
|
||||
[ domainObject ] : [],
|
||||
tail = result || [];
|
||||
return head.concat(tail);
|
||||
});
|
||||
}
|
||||
|
||||
// Invoke the observer callback to notify that new streaming
|
||||
// data has become available.
|
||||
function fireCallback() {
|
||||
callback();
|
||||
// Clear the pending flag so that future updates will
|
||||
// schedule this callback.
|
||||
updatePending = false;
|
||||
}
|
||||
|
||||
// Update the latest telemetry data for a specific
|
||||
// domain object. This will notify listeners.
|
||||
function update(domainObject, telemetry) {
|
||||
var count = telemetry && telemetry.getPointCount();
|
||||
|
||||
// Only schedule notification if there isn't already
|
||||
// a notification pending (and if we actually have
|
||||
// data)
|
||||
if (!updatePending && count) {
|
||||
updatePending = true;
|
||||
$timeout(fireCallback, 0);
|
||||
}
|
||||
|
||||
// Update the latest-value table
|
||||
if (count > 0) {
|
||||
latestValues[domainObject.getId()] = {
|
||||
domain: telemetry.getDomainValue(count - 1),
|
||||
range: telemetry.getRangeValue(count - 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare a subscription to a specific telemetry-providing
|
||||
// domain object.
|
||||
function subscribe(domainObject) {
|
||||
var telemetryCapability =
|
||||
domainObject.getCapability("telemetry");
|
||||
return telemetryCapability.subscribe(function (telemetry) {
|
||||
update(domainObject, telemetry);
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare subscriptions to all relevant telemetry-providing
|
||||
// domain objects.
|
||||
function subscribeAll(domainObjects) {
|
||||
return domainObjects.map(subscribe);
|
||||
}
|
||||
|
||||
// Cache a reference to all relevant telemetry-providing
|
||||
// domain objects. This will be called during the
|
||||
// initial subscription chain; this allows `getTelemetryObjects()`
|
||||
// to return a non-Promise to simplify usage elsewhere.
|
||||
function cacheObjectReferences(objects) {
|
||||
telemetryObjects = objects;
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Get a reference to relevant objects (those with telemetry
|
||||
// capabilities) and subscribe to their telemetry updates.
|
||||
// Keep a reference to their promised return values, as these
|
||||
// will be unsubscribe functions. (This must be a promise
|
||||
// because delegation is supported, and retrieving delegate
|
||||
// telemetry-capable objects may be an asynchronous operation.)
|
||||
unsubscribePromise =
|
||||
promiseRelevantObjects(domainObject)
|
||||
.then(cacheObjectReferences)
|
||||
.then(subscribeAll);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Terminate all underlying subscriptions associated
|
||||
* with this object.
|
||||
* @method
|
||||
* @memberof TelemetrySubscription
|
||||
*/
|
||||
unsubscribe: function () {
|
||||
return unsubscribePromise.then(function (unsubscribes) {
|
||||
return $q.all(unsubscribes.map(function (unsubscribe) {
|
||||
return unsubscribe();
|
||||
}));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get the most recent domain value that has been observed
|
||||
* for the specified domain object. This will typically be
|
||||
* a timestamp.
|
||||
*
|
||||
* The domain object passed here should be one that is
|
||||
* subscribed-to here; that is, it should be one of the
|
||||
* domain objects returned by `getTelemetryObjects()`.
|
||||
*
|
||||
* @param {DomainObject} domainObject the object of interest
|
||||
* @returns the most recent domain value observed
|
||||
* @method
|
||||
* @memberof TelemetrySubscription
|
||||
*/
|
||||
getDomainValue: function (domainObject) {
|
||||
var id = domainObject.getId();
|
||||
return (latestValues[id] || {}).domain;
|
||||
},
|
||||
/**
|
||||
* Get the most recent range value that has been observed
|
||||
* for the specified domain object. This will typically
|
||||
* be a numeric measurement.
|
||||
*
|
||||
* The domain object passed here should be one that is
|
||||
* subscribed-to here; that is, it should be one of the
|
||||
* domain objects returned by `getTelemetryObjects()`.
|
||||
*
|
||||
* @param {DomainObject} domainObject the object of interest
|
||||
* @returns the most recent range value observed
|
||||
* @method
|
||||
* @memberof TelemetrySubscription
|
||||
*/
|
||||
getRangeValue: function (domainObject) {
|
||||
var id = domainObject.getId();
|
||||
return (latestValues[id] || {}).range;
|
||||
},
|
||||
/**
|
||||
* Get all telemetry-providing domain objects which are
|
||||
* being observed as part of this subscription.
|
||||
*
|
||||
* Capability delegation will be taken into account (so, if
|
||||
* a Telemetry Panel was passed in the constructor, this will
|
||||
* return its contents.) Capability delegation is resolved
|
||||
* asynchronously so the return value here may change over
|
||||
* time; while this resolution is pending, this method will
|
||||
* return an empty array.
|
||||
*
|
||||
* @returns {DomainObject[]} all subscribed-to domain objects
|
||||
* @method
|
||||
* @memberof TelemetrySubscription
|
||||
*/
|
||||
getTelemetryObjects: function () {
|
||||
return telemetryObjects;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return TelemetrySubscription;
|
||||
|
||||
}
|
||||
);
|
@ -8,6 +8,7 @@ define(
|
||||
describe("The telemetry aggregator", function () {
|
||||
var mockQ,
|
||||
mockProviders,
|
||||
mockUnsubscribes,
|
||||
aggregator;
|
||||
|
||||
function mockPromise(value) {
|
||||
@ -20,10 +21,15 @@ define(
|
||||
|
||||
function mockProvider(key, index) {
|
||||
var provider = jasmine.createSpyObj(
|
||||
"provider" + index,
|
||||
[ "requestTelemetry" ]
|
||||
);
|
||||
"provider" + index,
|
||||
[ "requestTelemetry", "subscribe" ]
|
||||
),
|
||||
unsubscribe = jasmine.createSpy("unsubscribe" + index);
|
||||
provider.requestTelemetry.andReturn({ someKey: key });
|
||||
provider.subscribe.andReturn(unsubscribe);
|
||||
|
||||
// Store to verify interactions later
|
||||
mockUnsubscribes[index] = unsubscribe;
|
||||
return provider;
|
||||
}
|
||||
|
||||
@ -31,6 +37,7 @@ define(
|
||||
mockQ = jasmine.createSpyObj("$q", [ "all" ]);
|
||||
mockQ.all.andReturn(mockPromise([]));
|
||||
|
||||
mockUnsubscribes = [];
|
||||
mockProviders = [ "a", "b", "c" ].map(mockProvider);
|
||||
|
||||
aggregator = new TelemetryAggregator(mockQ, mockProviders);
|
||||
@ -74,6 +81,24 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("broadcasts subscriptions from all providers", function () {
|
||||
var mockCallback = jasmine.createSpy("callback"),
|
||||
subscription = aggregator.subscribe(mockCallback);
|
||||
|
||||
// Make sure all providers got subscribed to
|
||||
mockProviders.forEach(function (mockProvider) {
|
||||
expect(mockProvider.subscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Verify that unsubscription gets broadcast too
|
||||
mockUnsubscribes.forEach(function (mockUnsubscribe) {
|
||||
expect(mockUnsubscribe).not.toHaveBeenCalled();
|
||||
});
|
||||
subscription(); // unsubscribe
|
||||
mockUnsubscribes.forEach(function (mockUnsubscribe) {
|
||||
expect(mockUnsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ define(
|
||||
mockDomainObject,
|
||||
mockTelemetryService,
|
||||
mockReject,
|
||||
mockUnsubscribe,
|
||||
telemetry;
|
||||
|
||||
|
||||
@ -33,9 +34,10 @@ define(
|
||||
);
|
||||
mockTelemetryService = jasmine.createSpyObj(
|
||||
"telemetryService",
|
||||
[ "requestTelemetry" ]
|
||||
[ "requestTelemetry", "subscribe" ]
|
||||
);
|
||||
mockReject = jasmine.createSpyObj("reject", ["then"]);
|
||||
mockUnsubscribe = jasmine.createSpy("unsubscribe");
|
||||
|
||||
mockInjector.get.andReturn(mockTelemetryService);
|
||||
|
||||
@ -50,6 +52,11 @@ define(
|
||||
}
|
||||
});
|
||||
|
||||
mockTelemetryService.requestTelemetry
|
||||
.andReturn(mockPromise({}));
|
||||
mockTelemetryService.subscribe
|
||||
.andReturn(mockUnsubscribe);
|
||||
|
||||
// Bubble up...
|
||||
mockReject.then.andReturn(mockReject);
|
||||
|
||||
@ -124,6 +131,36 @@ define(
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows subscriptions to updates", function () {
|
||||
var mockCallback = jasmine.createSpy("callback"),
|
||||
subscription = telemetry.subscribe(mockCallback);
|
||||
|
||||
// Verify subscription to the appropriate object
|
||||
expect(mockTelemetryService.subscribe).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
[{
|
||||
id: "testId", // from domain object
|
||||
source: "testSource",
|
||||
key: "testKey"
|
||||
}]
|
||||
);
|
||||
|
||||
// Check that the callback gets invoked
|
||||
expect(mockCallback).not.toHaveBeenCalled();
|
||||
mockTelemetryService.subscribe.mostRecentCall.args[0]({
|
||||
testSource: { testKey: { someKey: "some value" } }
|
||||
});
|
||||
expect(mockCallback).toHaveBeenCalledWith(
|
||||
{ someKey: "some value" }
|
||||
);
|
||||
|
||||
// Finally, unsubscribe
|
||||
expect(mockUnsubscribe).not.toHaveBeenCalled();
|
||||
subscription(); // should be an unsubscribe function
|
||||
expect(mockUnsubscribe).toHaveBeenCalled();
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -187,6 +187,13 @@ define(
|
||||
.toHaveBeenCalledWith("telemetryUpdate");
|
||||
});
|
||||
|
||||
it("listens for scope destruction to clean up", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
/**
|
||||
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../src/TelemetryFormatter"],
|
||||
function (TelemetryFormatter) {
|
||||
|
54
platform/telemetry/test/TelemetrySubscriberSpec.js
Normal file
54
platform/telemetry/test/TelemetrySubscriberSpec.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TelemetrySubscriber"],
|
||||
function (TelemetrySubscriber) {
|
||||
"use strict";
|
||||
|
||||
describe("The telemetry subscriber", function () {
|
||||
// TelemetrySubscriber just provides a factory
|
||||
// for TelemetrySubscription, so most real testing
|
||||
// should happen there.
|
||||
var mockQ,
|
||||
mockTimeout,
|
||||
mockDomainObject,
|
||||
mockCallback,
|
||||
mockPromise,
|
||||
subscriber;
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getCapability", "useCapability", "hasCapability" ]
|
||||
);
|
||||
mockCallback = jasmine.createSpy("callback");
|
||||
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||
|
||||
mockQ.when.andReturn(mockPromise);
|
||||
mockPromise.then.andReturn(mockPromise);
|
||||
|
||||
subscriber = new TelemetrySubscriber(mockQ, mockTimeout);
|
||||
});
|
||||
|
||||
it("acts as a factory for subscription objects", function () {
|
||||
var subscription = subscriber.subscribe(
|
||||
mockDomainObject,
|
||||
mockCallback
|
||||
);
|
||||
// Just verify that this looks like a TelemetrySubscription
|
||||
[
|
||||
"unsubscribe",
|
||||
"getTelemetryObjects",
|
||||
"getRangeValue",
|
||||
"getDomainValue"
|
||||
].forEach(function (method) {
|
||||
expect(subscription[method])
|
||||
.toEqual(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
125
platform/telemetry/test/TelemetrySubscriptionSpec.js
Normal file
125
platform/telemetry/test/TelemetrySubscriptionSpec.js
Normal file
@ -0,0 +1,125 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/TelemetrySubscription"],
|
||||
function (TelemetrySubscription) {
|
||||
"use strict";
|
||||
|
||||
describe("A telemetry subscription", function () {
|
||||
var mockQ,
|
||||
mockTimeout,
|
||||
mockDomainObject,
|
||||
mockCallback,
|
||||
mockTelemetry,
|
||||
mockUnsubscribe,
|
||||
mockSeries,
|
||||
subscription;
|
||||
|
||||
function mockPromise(value) {
|
||||
return (value && value.then) ? value : {
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj("$q", ["when", "all"]);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getCapability", "useCapability", "hasCapability", "getId" ]
|
||||
);
|
||||
mockCallback = jasmine.createSpy("callback");
|
||||
mockTelemetry = jasmine.createSpyObj(
|
||||
"telemetry",
|
||||
["subscribe"]
|
||||
);
|
||||
mockUnsubscribe = jasmine.createSpy("unsubscribe");
|
||||
mockSeries = jasmine.createSpyObj(
|
||||
"series",
|
||||
[ "getPointCount", "getDomainValue", "getRangeValue" ]
|
||||
);
|
||||
|
||||
mockQ.when.andCallFake(mockPromise);
|
||||
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andReturn(mockTelemetry);
|
||||
mockDomainObject.getId.andReturn('test-id');
|
||||
|
||||
mockTelemetry.subscribe.andReturn(mockUnsubscribe);
|
||||
|
||||
mockSeries.getPointCount.andReturn(42);
|
||||
mockSeries.getDomainValue.andReturn(123456);
|
||||
mockSeries.getRangeValue.andReturn(789);
|
||||
|
||||
subscription = new TelemetrySubscription(
|
||||
mockQ,
|
||||
mockTimeout,
|
||||
mockDomainObject,
|
||||
mockCallback
|
||||
);
|
||||
});
|
||||
|
||||
it("subscribes to the provided object", function () {
|
||||
expect(mockTelemetry.subscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("unsubscribes on request", function () {
|
||||
expect(mockUnsubscribe).not.toHaveBeenCalled();
|
||||
subscription.unsubscribe();
|
||||
expect(mockUnsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fires callbacks when subscriptions update", function () {
|
||||
expect(mockCallback).not.toHaveBeenCalled();
|
||||
mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries);
|
||||
// This gets fired via a timeout, so trigger that
|
||||
expect(mockTimeout).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
0
|
||||
);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
// Should have triggered the callback to alert that
|
||||
// new data was available
|
||||
expect(mockCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fires subscription callbacks once per cycle", function () {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < 100; i += 1) {
|
||||
mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries);
|
||||
}
|
||||
// This gets fired via a timeout, so trigger any of those
|
||||
mockTimeout.calls.forEach(function (call) {
|
||||
call.args[0]();
|
||||
});
|
||||
// Should have only triggered the
|
||||
expect(mockCallback.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("reports its latest observed data values", function () {
|
||||
mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries);
|
||||
// This gets fired via a timeout, so trigger that
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
// Verify that the last sample was looked at
|
||||
expect(mockSeries.getDomainValue).toHaveBeenCalledWith(41);
|
||||
expect(mockSeries.getRangeValue).toHaveBeenCalledWith(41);
|
||||
// Domain and range values should now be available
|
||||
expect(subscription.getDomainValue(mockDomainObject))
|
||||
.toEqual(123456);
|
||||
expect(subscription.getRangeValue(mockDomainObject))
|
||||
.toEqual(789);
|
||||
});
|
||||
|
||||
it("provides no objects if no domain object is provided", function () {
|
||||
// omit last arguments
|
||||
subscription = new TelemetrySubscription(mockQ, mockTimeout);
|
||||
|
||||
// Should have no objects
|
||||
expect(subscription.getTelemetryObjects()).toEqual([]);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -2,5 +2,7 @@
|
||||
"TelemetryAggregator",
|
||||
"TelemetryCapability",
|
||||
"TelemetryController",
|
||||
"TelemetryFormatter"
|
||||
"TelemetryFormatter",
|
||||
"TelemetrySubscriber",
|
||||
"TelemetrySubscription"
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user