mirror of
https://github.com/nasa/openmct.git
synced 2025-01-29 15:43:52 +00:00
Merge pull request #1522 from nasa/open1494-other
[Fixed Position] Updated fixed position view to use new Telemetry API
This commit is contained in:
commit
2a173f67c8
@ -15,7 +15,8 @@
|
||||
"predef": [
|
||||
"define",
|
||||
"Promise",
|
||||
"WeakMap"
|
||||
"WeakMap",
|
||||
"Map"
|
||||
],
|
||||
"shadow": "outer",
|
||||
"strict": "implied",
|
||||
|
@ -237,9 +237,7 @@ define([
|
||||
"$scope",
|
||||
"$q",
|
||||
"dialogService",
|
||||
"telemetryHandler",
|
||||
"telemetryFormatter",
|
||||
"throttle"
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -21,8 +21,20 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['./FixedProxy', './elements/ElementProxies', './FixedDragHandle'],
|
||||
function (FixedProxy, ElementProxies, FixedDragHandle) {
|
||||
[
|
||||
'lodash',
|
||||
'./FixedProxy',
|
||||
'./elements/ElementProxies',
|
||||
'./FixedDragHandle',
|
||||
'../../../../src/api/objects/object-utils'
|
||||
],
|
||||
function (
|
||||
_,
|
||||
FixedProxy,
|
||||
ElementProxies,
|
||||
FixedDragHandle,
|
||||
objectUtils
|
||||
) {
|
||||
|
||||
var DEFAULT_DIMENSIONS = [2, 1];
|
||||
|
||||
@ -35,13 +47,30 @@ define(
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function FixedController($scope, $q, dialogService, telemetryHandler, telemetryFormatter) {
|
||||
var self = this,
|
||||
handle,
|
||||
names = {}, // Cache names by ID
|
||||
values = {}, // Cache values by ID
|
||||
elementProxiesById = {},
|
||||
maxDomainValue = Number.POSITIVE_INFINITY;
|
||||
function FixedController($scope, $q, dialogService, openmct) {
|
||||
this.names = {}; // Cache names by ID
|
||||
this.values = {}; // Cache values by ID
|
||||
this.elementProxiesById = {};
|
||||
|
||||
this.telemetryObjects = [];
|
||||
this.subscriptions = [];
|
||||
this.openmct = openmct;
|
||||
this.$scope = $scope;
|
||||
|
||||
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
||||
|
||||
var self = this;
|
||||
[
|
||||
'digest',
|
||||
'fetchHistoricalData',
|
||||
'getTelemetry',
|
||||
'setDisplayedValue',
|
||||
'subscribeToObjects',
|
||||
'unsubscribe',
|
||||
'updateView'
|
||||
].forEach(function (name) {
|
||||
self[name] = self[name].bind(self);
|
||||
});
|
||||
|
||||
// Convert from element x/y/width/height to an
|
||||
// appropriate ng-style argument, to position elements.
|
||||
@ -79,55 +108,6 @@ define(
|
||||
return element.handles().map(generateDragHandle);
|
||||
}
|
||||
|
||||
// Update the value displayed in elements of this telemetry object
|
||||
function setDisplayedValue(telemetryObject, value, alarm) {
|
||||
var id = telemetryObject.getId();
|
||||
(elementProxiesById[id] || []).forEach(function (element) {
|
||||
names[id] = telemetryObject.getModel().name;
|
||||
values[id] = telemetryFormatter.formatRangeValue(value);
|
||||
element.name = names[id];
|
||||
element.value = values[id];
|
||||
element.cssClass = alarm && alarm.cssClass;
|
||||
});
|
||||
}
|
||||
|
||||
// Update the displayed value for this object, from a specific
|
||||
// telemetry series
|
||||
function updateValueFromSeries(telemetryObject, telemetrySeries) {
|
||||
var index = telemetrySeries.getPointCount() - 1,
|
||||
limit = telemetryObject &&
|
||||
telemetryObject.getCapability('limit'),
|
||||
datum = telemetryObject && handle.getDatum(
|
||||
telemetryObject,
|
||||
index
|
||||
);
|
||||
|
||||
if (index >= 0) {
|
||||
setDisplayedValue(
|
||||
telemetryObject,
|
||||
telemetrySeries.getRangeValue(index),
|
||||
limit && datum && limit.evaluate(datum)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the displayed value for this object
|
||||
function updateValue(telemetryObject) {
|
||||
var limit = telemetryObject &&
|
||||
telemetryObject.getCapability('limit'),
|
||||
datum = telemetryObject &&
|
||||
handle.getDatum(telemetryObject);
|
||||
|
||||
if (telemetryObject &&
|
||||
(handle.getDomainValue(telemetryObject) < maxDomainValue)) {
|
||||
setDisplayedValue(
|
||||
telemetryObject,
|
||||
handle.getRangeValue(telemetryObject),
|
||||
limit && datum && limit.evaluate(datum)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update element positions when grid size changes
|
||||
function updateElementPositions(layoutGrid) {
|
||||
// Update grid size from model
|
||||
@ -138,13 +118,6 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
// Update telemetry values based on new data available
|
||||
function updateValues() {
|
||||
if (handle) {
|
||||
handle.getTelemetryObjects().forEach(updateValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Decorate an element for display
|
||||
function makeProxyElement(element, index, elements) {
|
||||
var ElementProxy = ElementProxies[element.type],
|
||||
@ -186,64 +159,57 @@ define(
|
||||
|
||||
// Finally, rebuild lists of elements by id to
|
||||
// facilitate faster update when new telemetry comes in.
|
||||
elementProxiesById = {};
|
||||
self.elementProxiesById = {};
|
||||
self.elementProxies.forEach(function (elementProxy) {
|
||||
var id = elementProxy.id;
|
||||
if (elementProxy.element.type === 'fixed.telemetry') {
|
||||
// Provide it a cached name/value to avoid flashing
|
||||
elementProxy.name = names[id];
|
||||
elementProxy.value = values[id];
|
||||
elementProxiesById[id] = elementProxiesById[id] || [];
|
||||
elementProxiesById[id].push(elementProxy);
|
||||
elementProxy.name = self.names[id];
|
||||
elementProxy.value = self.values[id];
|
||||
self.elementProxiesById[id] = self.elementProxiesById[id] || [];
|
||||
self.elementProxiesById[id].push(elementProxy);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Ensure elements for all domain objects?
|
||||
}
|
||||
|
||||
// Free up subscription to telemetry
|
||||
function releaseSubscription() {
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
handle = undefined;
|
||||
}
|
||||
}
|
||||
function removeObjects(ids) {
|
||||
var configuration = self.$scope.configuration;
|
||||
|
||||
// Subscribe to telemetry updates for this domain object
|
||||
function subscribe(domainObject) {
|
||||
// Release existing subscription (if any)
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
if (configuration &&
|
||||
configuration.elements) {
|
||||
configuration.elements = configuration.elements.filter(function (proxy) {
|
||||
return ids.indexOf(proxy.id) === -1;
|
||||
});
|
||||
}
|
||||
self.getTelemetry($scope.domainObject);
|
||||
refreshElements();
|
||||
// Mark change as persistable
|
||||
if (self.$scope.commit) {
|
||||
self.$scope.commit("Objects removed.");
|
||||
}
|
||||
|
||||
// Make a new subscription
|
||||
handle = domainObject && telemetryHandler.handle(
|
||||
domainObject,
|
||||
updateValues
|
||||
);
|
||||
// Request an initial historical telemetry value
|
||||
handle.request(
|
||||
{ size: 1 }, // Only need a single data point
|
||||
updateValueFromSeries
|
||||
);
|
||||
}
|
||||
|
||||
// Handle changes in the object's composition
|
||||
function updateComposition() {
|
||||
// Populate panel positions
|
||||
// TODO: Ensure defaults here
|
||||
function updateComposition(composition, previousComposition) {
|
||||
var removedIds = [];
|
||||
// Resubscribe - objects in view have changed
|
||||
subscribe($scope.domainObject);
|
||||
if (composition !== previousComposition) {
|
||||
//remove any elements no longer in the composition
|
||||
removedIds = _.difference(previousComposition, composition);
|
||||
if (removedIds.length > 0) {
|
||||
removeObjects(removedIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a new query for telemetry data
|
||||
function updateDisplayBounds(event, bounds) {
|
||||
maxDomainValue = bounds.end;
|
||||
if (handle) {
|
||||
handle.request(
|
||||
{ size: 1 }, // Only need a single data point
|
||||
updateValueFromSeries
|
||||
);
|
||||
function updateDisplayBounds(bounds) {
|
||||
if (!self.openmct.conductor.follow()) {
|
||||
//Reset values
|
||||
self.values = {};
|
||||
refreshElements();
|
||||
//Fetch new data
|
||||
self.fetchHistoricalData(self.telemetryObjects);
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,6 +256,9 @@ define(
|
||||
width: DEFAULT_DIMENSIONS[0],
|
||||
height: DEFAULT_DIMENSIONS[1]
|
||||
});
|
||||
|
||||
//Re-initialize objects, and subscribe to new object
|
||||
self.getTelemetry($scope.domainObject);
|
||||
}
|
||||
|
||||
this.elementProxies = [];
|
||||
@ -311,25 +280,167 @@ define(
|
||||
// Detect changes to grid size
|
||||
$scope.$watch("model.layoutGrid", updateElementPositions);
|
||||
|
||||
// Refresh list of elements whenever model changes
|
||||
$scope.$watch("model.modified", refreshElements);
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
// Position panes when the model field changes
|
||||
$scope.$watch("model.composition", updateComposition);
|
||||
|
||||
// Refresh list of elements whenever model changes
|
||||
$scope.$watch("model.modified", refreshElements);
|
||||
|
||||
// Subscribe to telemetry when an object is available
|
||||
$scope.$watch("domainObject", subscribe);
|
||||
$scope.$watch("domainObject", this.getTelemetry);
|
||||
|
||||
// Free up subscription on destroy
|
||||
$scope.$on("$destroy", releaseSubscription);
|
||||
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
$scope.$on("$destroy", function () {
|
||||
self.unsubscribe();
|
||||
self.openmct.conductor.off("bounds", updateDisplayBounds);
|
||||
});
|
||||
|
||||
// Respond to external bounds changes
|
||||
$scope.$on("telemetry:display:bounds", updateDisplayBounds);
|
||||
this.openmct.conductor.on("bounds", updateDisplayBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* A rate-limited digest function. Caps digests at 60Hz
|
||||
* @private
|
||||
*/
|
||||
FixedController.prototype.digest = function () {
|
||||
var self = this;
|
||||
|
||||
if (!this.digesting) {
|
||||
this.digesting = true;
|
||||
requestAnimationFrame(function () {
|
||||
self.$scope.$digest();
|
||||
self.digesting = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribe all listeners
|
||||
* @private
|
||||
*/
|
||||
FixedController.prototype.unsubscribe = function () {
|
||||
this.subscriptions.forEach(function (unsubscribeFunc) {
|
||||
unsubscribeFunc();
|
||||
});
|
||||
this.subscriptions = [];
|
||||
this.telemetryObjects = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe to all given domain objects
|
||||
* @private
|
||||
* @param {object[]} objects Domain objects to subscribe to
|
||||
* @returns {object[]} The provided objects, for chaining.
|
||||
*/
|
||||
FixedController.prototype.subscribeToObjects = function (objects) {
|
||||
var self = this;
|
||||
this.subscriptions = objects.map(function (object) {
|
||||
return self.openmct.telemetry.subscribe(object, function (datum) {
|
||||
if (self.openmct.conductor.follow()) {
|
||||
self.updateView(object, datum);
|
||||
}
|
||||
}, {});
|
||||
});
|
||||
return objects;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print the values from the given datum against the provided object in the view.
|
||||
* @private
|
||||
* @param {object} telemetryObject The domain object associated with the given telemetry data
|
||||
* @param {object} datum The telemetry datum containing the values to print
|
||||
*/
|
||||
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
||||
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
var rangeMetadata = metadata.valuesForHints(['range'])[0];
|
||||
var rangeKey = rangeMetadata.source || rangeMetadata.key;
|
||||
var valueMetadata = metadata.value(rangeKey);
|
||||
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
||||
var value = datum[valueMetadata.key];
|
||||
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, rangeKey);
|
||||
|
||||
this.setDisplayedValue(
|
||||
telemetryObject,
|
||||
formatter.format(value),
|
||||
alarm && alarm.cssClass
|
||||
);
|
||||
this.digest();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request the last historical data point for the given domain objects
|
||||
* @param {object[]} objects
|
||||
* @returns {object[]} the provided objects for chaining.
|
||||
*/
|
||||
FixedController.prototype.fetchHistoricalData = function (objects) {
|
||||
var bounds = this.openmct.conductor.bounds();
|
||||
var self = this;
|
||||
|
||||
objects.forEach(function (object) {
|
||||
self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
|
||||
.then(function (data) {
|
||||
self.updateView(object, data[data.length - 1]);
|
||||
});
|
||||
});
|
||||
return objects;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Print a value to the onscreen element associated with a given telemetry object.
|
||||
* @private
|
||||
* @param {object} telemetryObject The telemetry object associated with the value
|
||||
* @param {string | number} value The value to print to screen
|
||||
* @param {string} [cssClass] an optional CSS class to apply to the onscreen element.
|
||||
*/
|
||||
FixedController.prototype.setDisplayedValue = function (telemetryObject, value, cssClass) {
|
||||
var id = objectUtils.makeKeyString(telemetryObject.identifier);
|
||||
var self = this;
|
||||
|
||||
(self.elementProxiesById[id] || []).forEach(function (element) {
|
||||
self.names[id] = telemetryObject.name;
|
||||
self.values[id] = value;
|
||||
element.name = self.names[id];
|
||||
element.value = self.values[id];
|
||||
element.cssClass = cssClass;
|
||||
});
|
||||
};
|
||||
|
||||
FixedController.prototype.getTelemetry = function (domainObject) {
|
||||
var newObject = domainObject.useCapability('adapter');
|
||||
var self = this;
|
||||
|
||||
if (this.subscriptions.length > 0) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
function filterForTelemetryObjects(objects) {
|
||||
return objects.filter(function (object) {
|
||||
return self.openmct.telemetry.canProvideTelemetry(object);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeDisplay(objects) {
|
||||
self.telemetryObjects = objects;
|
||||
objects.forEach(function (object) {
|
||||
// Initialize values
|
||||
self.setDisplayedValue(object, "");
|
||||
});
|
||||
return objects;
|
||||
}
|
||||
|
||||
return this.openmct.composition.get(newObject).load()
|
||||
.then(filterForTelemetryObjects)
|
||||
.then(initializeDisplay)
|
||||
.then(this.fetchHistoricalData)
|
||||
.then(this.subscribeToObjects);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the size of the grid, in pixels. The returned array
|
||||
* is in the form `[x, y]`.
|
||||
|
@ -37,6 +37,15 @@ define(
|
||||
testModel,
|
||||
testValues,
|
||||
testConfiguration,
|
||||
mockOpenMCT,
|
||||
mockTelemetryAPI,
|
||||
mockCompositionAPI,
|
||||
mockCompositionCollection,
|
||||
mockChildren,
|
||||
mockConductor,
|
||||
mockMetadata,
|
||||
mockTimeSystem,
|
||||
mockLimitEvaluator,
|
||||
controller;
|
||||
|
||||
// Utility function; find a watch for a given expression
|
||||
@ -62,19 +71,18 @@ define(
|
||||
}
|
||||
|
||||
function makeMockDomainObject(id) {
|
||||
var mockObject = jasmine.createSpyObj(
|
||||
'domainObject-' + id,
|
||||
['getId', 'getModel', 'getCapability']
|
||||
);
|
||||
mockObject.getId.andReturn(id);
|
||||
mockObject.getModel.andReturn({ name: "Point " + id});
|
||||
return mockObject;
|
||||
return {
|
||||
identifier: {
|
||||
key: "domainObject-" + id
|
||||
},
|
||||
name: "Point " + id
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
'$scope',
|
||||
["$on", "$watch", "commit"]
|
||||
["$on", "$watch", "$digest", "commit"]
|
||||
);
|
||||
mockHandler = jasmine.createSpyObj(
|
||||
'telemetryHandler',
|
||||
@ -87,12 +95,17 @@ define(
|
||||
);
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
'telemetryFormatter',
|
||||
['formatDomainValue', 'formatRangeValue']
|
||||
['format']
|
||||
);
|
||||
mockFormatter.format.andCallFake(function (value) {
|
||||
return "Formatted " + value;
|
||||
});
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getModel', 'getCapability']
|
||||
['getId', 'getModel', 'getCapability', 'useCapability']
|
||||
);
|
||||
|
||||
mockHandle = jasmine.createSpyObj(
|
||||
'subscription',
|
||||
[
|
||||
@ -104,11 +117,39 @@ define(
|
||||
'request'
|
||||
]
|
||||
);
|
||||
mockConductor = jasmine.createSpyObj('conductor', [
|
||||
'on',
|
||||
'off',
|
||||
'bounds',
|
||||
'timeSystem',
|
||||
'follow'
|
||||
]);
|
||||
mockConductor.bounds.andReturn({});
|
||||
mockTimeSystem = {
|
||||
metadata: {
|
||||
key: 'key'
|
||||
}
|
||||
};
|
||||
mockConductor.timeSystem.andReturn(mockTimeSystem);
|
||||
|
||||
mockEvent = jasmine.createSpyObj(
|
||||
'event',
|
||||
['preventDefault']
|
||||
);
|
||||
|
||||
mockTelemetryAPI = jasmine.createSpyObj('telemetry',
|
||||
[
|
||||
'subscribe',
|
||||
'request',
|
||||
'canProvideTelemetry',
|
||||
'getMetadata',
|
||||
'limitEvaluator',
|
||||
'getValueFormatter'
|
||||
]
|
||||
);
|
||||
mockTelemetryAPI.canProvideTelemetry.andReturn(true);
|
||||
mockTelemetryAPI.request.andReturn(Promise.resolve([]));
|
||||
|
||||
testGrid = [123, 456];
|
||||
testModel = {
|
||||
composition: ['a', 'b', 'c'],
|
||||
@ -121,17 +162,23 @@ define(
|
||||
{ type: "fixed.telemetry", id: 'c', x: 1, y: 1 }
|
||||
]};
|
||||
|
||||
mockHandler.handle.andReturn(mockHandle);
|
||||
mockHandle.getTelemetryObjects.andReturn(
|
||||
testModel.composition.map(makeMockDomainObject)
|
||||
mockChildren = testModel.composition.map(makeMockDomainObject);
|
||||
mockCompositionCollection = jasmine.createSpyObj('compositionCollection',
|
||||
[
|
||||
'load'
|
||||
]
|
||||
);
|
||||
mockHandle.getRangeValue.andCallFake(function (o) {
|
||||
return testValues[o.getId()];
|
||||
});
|
||||
mockHandle.getDomainValue.andReturn(12321);
|
||||
mockFormatter.formatRangeValue.andCallFake(function (v) {
|
||||
return "Formatted " + v;
|
||||
});
|
||||
mockCompositionAPI = jasmine.createSpyObj('composition',
|
||||
[
|
||||
'get'
|
||||
]
|
||||
);
|
||||
mockCompositionAPI.get.andReturn(mockCompositionCollection);
|
||||
mockCompositionCollection.load.andReturn(
|
||||
Promise.resolve(mockChildren)
|
||||
);
|
||||
|
||||
|
||||
mockScope.model = testModel;
|
||||
mockScope.configuration = testConfiguration;
|
||||
mockScope.selection = jasmine.createSpyObj(
|
||||
@ -139,12 +186,47 @@ define(
|
||||
['select', 'get', 'selected', 'deselect', 'proxy']
|
||||
);
|
||||
|
||||
mockOpenMCT = {
|
||||
conductor: mockConductor,
|
||||
telemetry: mockTelemetryAPI,
|
||||
composition: mockCompositionAPI
|
||||
};
|
||||
|
||||
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
||||
'valuesForHints',
|
||||
'value'
|
||||
]);
|
||||
mockMetadata.value.andReturn({
|
||||
key: 'value'
|
||||
});
|
||||
|
||||
mockMetadata.valuesForHints.andCallFake(function (hints) {
|
||||
if (hints === ['domain']) {
|
||||
return [{
|
||||
key: 'time'
|
||||
}];
|
||||
} else {
|
||||
return [{
|
||||
key: 'value'
|
||||
}];
|
||||
}
|
||||
});
|
||||
|
||||
mockLimitEvaluator = jasmine.createSpyObj('limitEvaluator', [
|
||||
'evaluate'
|
||||
]);
|
||||
|
||||
mockLimitEvaluator.evaluate.andReturn({});
|
||||
|
||||
mockTelemetryAPI.getMetadata.andReturn(mockMetadata);
|
||||
mockTelemetryAPI.limitEvaluator.andReturn(mockLimitEvaluator);
|
||||
mockTelemetryAPI.getValueFormatter.andReturn(mockFormatter);
|
||||
|
||||
controller = new FixedController(
|
||||
mockScope,
|
||||
mockQ,
|
||||
mockDialogService,
|
||||
mockHandler,
|
||||
mockFormatter
|
||||
mockOpenMCT
|
||||
);
|
||||
|
||||
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
||||
@ -152,26 +234,61 @@ define(
|
||||
});
|
||||
|
||||
it("subscribes when a domain object is available", function () {
|
||||
var dunzo = false;
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockHandler.handle).toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
|
||||
runs(function () {
|
||||
mockChildren.forEach(function (child) {
|
||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(
|
||||
child,
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("releases subscriptions when domain objects change", function () {
|
||||
var dunzo = false;
|
||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
|
||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
});
|
||||
|
||||
// First pass - should simply should subscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(1);
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
|
||||
// Object changes - should unsubscribe then resubscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(2);
|
||||
runs(function () {
|
||||
expect(unsubscribe).not.toHaveBeenCalled();
|
||||
|
||||
dunzo = false;
|
||||
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes visible elements based on configuration", function () {
|
||||
@ -254,25 +371,38 @@ define(
|
||||
expect(mockScope.selection.select.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("provides values for telemetry elements", function () {
|
||||
it("Displays received values for telemetry elements", function () {
|
||||
var elements;
|
||||
// Initialize
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
findWatch("model.modified")(1);
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
var mockTelemetry = {
|
||||
time: 100,
|
||||
value: 200
|
||||
};
|
||||
var testElement = {};
|
||||
var telemetryObject = {
|
||||
identifier: {
|
||||
key: '12345'
|
||||
}
|
||||
};
|
||||
controller.elementProxiesById = {};
|
||||
controller.elementProxiesById['12345'] = [testElement];
|
||||
controller.elementProxies = [testElement];
|
||||
|
||||
// Invoke the subscription callback
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
controller.subscribeToObjects([telemetryObject]);
|
||||
mockConductor.follow.andReturn(true);
|
||||
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
|
||||
|
||||
// Get elements that controller is now exposing
|
||||
elements = controller.getElements();
|
||||
waitsFor(function () {
|
||||
return controller.digesting === false;
|
||||
}, "digest to complete", 100);
|
||||
|
||||
runs(function () {
|
||||
// Get elements that controller is now exposing
|
||||
elements = controller.getElements();
|
||||
|
||||
// Formatted values should be available
|
||||
expect(elements[0].value).toEqual("Formatted 200");
|
||||
});
|
||||
|
||||
// Formatted values should be available
|
||||
expect(elements[0].value).toEqual("Formatted 10");
|
||||
expect(elements[1].value).toEqual("Formatted 42");
|
||||
expect(elements[2].value).toEqual("Formatted 31.42");
|
||||
});
|
||||
|
||||
it("updates elements styles when grid size changes", function () {
|
||||
@ -291,6 +421,9 @@ define(
|
||||
});
|
||||
|
||||
it("listens for drop events", function () {
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
|
||||
// Layout should position panels according to
|
||||
// where the user dropped them, so it needs to
|
||||
// listen for drop events.
|
||||
@ -339,14 +472,29 @@ define(
|
||||
});
|
||||
|
||||
it("unsubscribes when destroyed", function () {
|
||||
// Make an object available
|
||||
findWatch('domainObject')(mockDomainObject);
|
||||
// Also verify precondition
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
findOn('$destroy')();
|
||||
// Should have unsubscribed
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
|
||||
var dunzo = false;
|
||||
var unsubscribe = jasmine.createSpy('unsubscribe');
|
||||
|
||||
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject).then(function () {
|
||||
dunzo = true;
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return dunzo;
|
||||
}, "Telemetry fetched", 200);
|
||||
|
||||
runs(function () {
|
||||
expect(unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
findOn('$destroy')();
|
||||
|
||||
//Check that the same unsubscribe function returned by the
|
||||
expect(unsubscribe.calls.length).toBe(mockChildren.length);
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes its grid size", function () {
|
||||
@ -427,92 +575,102 @@ define(
|
||||
|
||||
describe("on display bounds changes", function () {
|
||||
var testBounds;
|
||||
var boundsChangeCallback;
|
||||
var objectOne;
|
||||
var objectTwo;
|
||||
|
||||
beforeEach(function () {
|
||||
testBounds = { start: 123, end: 321 };
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
findWatch("model.modified")(testModel.modified);
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
findOn('telemetry:display:bounds')({}, testBounds);
|
||||
boundsChangeCallback = mockConductor.on.mostRecentCall.args[1];
|
||||
objectOne = {};
|
||||
objectTwo = {};
|
||||
controller.telemetryObjects = [
|
||||
objectOne,
|
||||
objectTwo
|
||||
];
|
||||
spyOn(controller, "fetchHistoricalData");
|
||||
controller.fetchHistoricalData.andCallThrough();
|
||||
});
|
||||
|
||||
it("issues new requests", function () {
|
||||
expect(mockHandle.request).toHaveBeenCalled();
|
||||
it("registers a bounds change listener", function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("requests only a single point", function () {
|
||||
expect(mockHandle.request.mostRecentCall.args[0].size)
|
||||
.toEqual(1);
|
||||
mockConductor.follow.andReturn(false);
|
||||
boundsChangeCallback(testBounds);
|
||||
expect(mockTelemetryAPI.request.calls.length).toBe(2);
|
||||
|
||||
mockTelemetryAPI.request.calls.forEach(function (call) {
|
||||
expect(call.args[1].size).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and after data has been received", function () {
|
||||
var mockSeries,
|
||||
testValue;
|
||||
it("Does not fetch historical data on tick", function () {
|
||||
mockConductor.follow.andReturn(true);
|
||||
boundsChangeCallback(testBounds);
|
||||
expect(mockTelemetryAPI.request.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
testValue = 12321;
|
||||
describe("on receipt of telemetry", function () {
|
||||
var mockTelemetryObject;
|
||||
var testValue;
|
||||
var testElement;
|
||||
|
||||
mockSeries = jasmine.createSpyObj('series', [
|
||||
'getPointCount',
|
||||
'getDomainValue',
|
||||
'getRangeValue'
|
||||
]);
|
||||
mockSeries.getPointCount.andReturn(1);
|
||||
mockSeries.getRangeValue.andReturn(testValue);
|
||||
beforeEach(function () {
|
||||
mockTelemetryObject = {
|
||||
identifier: {
|
||||
key: '12345'
|
||||
}
|
||||
};
|
||||
testValue = 30;
|
||||
testElement = {};
|
||||
|
||||
// Fire the callback associated with the request
|
||||
mockHandle.request.mostRecentCall.args[1](
|
||||
mockHandle.getTelemetryObjects()[0],
|
||||
mockSeries
|
||||
);
|
||||
controller.elementProxiesById = {};
|
||||
controller.elementProxiesById['12345'] = [testElement];
|
||||
controller.elementProxies = [testElement];
|
||||
});
|
||||
|
||||
it("updates displayed values from historical telemetry", function () {
|
||||
spyOn(controller, "updateView");
|
||||
controller.updateView.andCallThrough();
|
||||
|
||||
mockTelemetryAPI.request.andReturn(Promise.resolve([{
|
||||
time: 100,
|
||||
value: testValue
|
||||
}]));
|
||||
|
||||
controller.fetchHistoricalData([mockTelemetryObject]);
|
||||
|
||||
waitsFor(function () {
|
||||
return controller.digesting === false;
|
||||
});
|
||||
|
||||
it("updates displayed values", function () {
|
||||
runs(function () {
|
||||
expect(controller.updateView).toHaveBeenCalled();
|
||||
expect(controller.getElements()[0].value)
|
||||
.toEqual("Formatted " + testValue);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
it("reflects limit status", function () {
|
||||
mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
|
||||
controller.updateView(mockTelemetryObject, [{
|
||||
time: 100,
|
||||
value: testValue
|
||||
}]);
|
||||
|
||||
it("reflects limit status", function () {
|
||||
var elements;
|
||||
|
||||
mockHandle.getDatum.andReturn({});
|
||||
mockHandle.getTelemetryObjects().forEach(function (mockObject) {
|
||||
var id = mockObject.getId(),
|
||||
mockLimitCapability =
|
||||
jasmine.createSpyObj('limit-' + id, ['evaluate']);
|
||||
|
||||
mockObject.getCapability.andCallFake(function (key) {
|
||||
return (key === 'limit') && mockLimitCapability;
|
||||
waitsFor(function () {
|
||||
return controller.digesting === false;
|
||||
});
|
||||
|
||||
mockLimitCapability.evaluate
|
||||
.andReturn({ cssClass: 'alarm-' + id });
|
||||
runs(function () {
|
||||
// Limit-based CSS classes should be available
|
||||
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
mockScope.model = testModel;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
findWatch("model.modified")(1);
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
|
||||
// Invoke the subscription callback
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
|
||||
// Get elements that controller is now exposing
|
||||
elements = controller.getElements();
|
||||
|
||||
// Limit-based CSS classes should be available
|
||||
expect(elements[0].cssClass).toEqual("alarm-a");
|
||||
expect(elements[1].cssClass).toEqual("alarm-b");
|
||||
expect(elements[2].cssClass).toEqual("alarm-c");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -75,6 +75,7 @@ define(
|
||||
// If collection is not sorted by a time field, we cannot respond to
|
||||
// bounds events
|
||||
if (this.sortField === undefined) {
|
||||
this.lastBounds = bounds;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -94,7 +95,7 @@ define(
|
||||
|
||||
if (discarded && discarded.length > 0) {
|
||||
/**
|
||||
* A `discarded` event is thrown when telemetry data fall out of
|
||||
* A `discarded` event is emitted when telemetry data fall out of
|
||||
* bounds due to a bounds change event
|
||||
* @type {object[]} discarded the telemetry data
|
||||
* discarded as a result of the bounds change
|
||||
@ -103,7 +104,7 @@ define(
|
||||
}
|
||||
if (added && added.length > 0) {
|
||||
/**
|
||||
* An `added` event is thrown when a bounds change results in
|
||||
* An `added` event is emitted when a bounds change results in
|
||||
* received telemetry falling within the new bounds.
|
||||
* @type {object[]} added the telemetry data that is now within bounds
|
||||
*/
|
||||
@ -194,11 +195,14 @@ define(
|
||||
*/
|
||||
TelemetryCollection.prototype.clear = function () {
|
||||
this.telemetry = [];
|
||||
this.highBuffer = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts the telemetry collection based on the provided sort field
|
||||
* specifier.
|
||||
* specifier. Subsequent inserts are sorted to maintain specified sport
|
||||
* order.
|
||||
*
|
||||
* @example
|
||||
* // First build some mock telemetry for the purpose of an example
|
||||
* let now = Date.now();
|
||||
|
Loading…
x
Reference in New Issue
Block a user