mirror of
https://github.com/nasa/openmct.git
synced 2025-01-30 08:04:03 +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": [
|
"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