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