Merge pull request #1522 from nasa/open1494-other

[Fixed Position] Updated fixed position view to use new Telemetry API
This commit is contained in:
Victor Woeltjen 2017-04-24 12:45:37 -07:00 committed by GitHub
commit 2a173f67c8
5 changed files with 512 additions and 240 deletions

View File

@ -15,7 +15,8 @@
"predef": [
"define",
"Promise",
"WeakMap"
"WeakMap",
"Map"
],
"shadow": "outer",
"strict": "implied",

View File

@ -237,9 +237,7 @@ define([
"$scope",
"$q",
"dialogService",
"telemetryHandler",
"telemetryFormatter",
"throttle"
"openmct"
]
}
],

View File

@ -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]`.

View File

@ -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");
});
});
}
);

View File

@ -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();