Merge remote-tracking branch 'origin/open972'

This commit is contained in:
Pete Richards 2016-06-16 11:08:43 -07:00
commit 8ac6ac97f0
6 changed files with 171 additions and 24 deletions

View File

@ -109,7 +109,7 @@ define([
{ {
"key": "HistoricalTableController", "key": "HistoricalTableController",
"implementation": HistoricalTableController, "implementation": HistoricalTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"] "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout"]
}, },
{ {
"key": "RealtimeTableController", "key": "RealtimeTableController",

View File

@ -1,4 +1,4 @@
<div ng-controller="HistoricalTableController"> <div ng-controller="HistoricalTableController" ng-class="{'loading': loading}">
<mct-table <mct-table
headers="headers" headers="headers"
rows="rows" rows="rows"

View File

@ -25,6 +25,7 @@ define(
'./TelemetryTableController' './TelemetryTableController'
], ],
function (TableController) { function (TableController) {
var BATCH_SIZE = 1000;
/** /**
* Extends TelemetryTableController and adds real-time streaming * Extends TelemetryTableController and adds real-time streaming
@ -35,32 +36,82 @@ define(
* @param telemetryFormatter * @param telemetryFormatter
* @constructor * @constructor
*/ */
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) { function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout) {
var self = this;
this.$timeout = $timeout;
this.timeoutHandle = undefined;
this.batchSize = BATCH_SIZE;
$scope.$on("$destroy", function () {
if (self.timeoutHandle) {
self.$timeout.cancel(self.timeoutHandle);
}
});
TableController.call(this, $scope, telemetryHandler, telemetryFormatter); TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
} }
HistoricalTableController.prototype = Object.create(TableController.prototype); HistoricalTableController.prototype = Object.create(TableController.prototype);
/** /**
* Populates historical data on scope when it becomes available from * Set provided row data on scope, and cancel loading spinner
* the telemetry API * @private
*/ */
HistoricalTableController.prototype.addHistoricalData = function () { HistoricalTableController.prototype.doneProcessing = function (rowData) {
var rowData = [],
self = this;
this.handle.getTelemetryObjects().forEach(function (telemetryObject) {
var series = self.handle.getSeries(telemetryObject) || {},
pointCount = series.getPointCount ? series.getPointCount() : 0,
i = 0;
for (; i < pointCount; i++) {
rowData.push(self.table.getRowValues(telemetryObject,
self.handle.makeDatum(telemetryObject, series, i)));
}
});
this.$scope.rows = rowData; this.$scope.rows = rowData;
this.$scope.loading = false;
};
/**
* Processes an array of objects, formatting the telemetry available
* for them and setting it on scope when done
* @private
*/
HistoricalTableController.prototype.processTelemetryObjects = function (objects, offset, start, rowData) {
var telemetryObject = objects[offset],
series,
i = start,
pointCount,
end;
//No more objects to process
if (!telemetryObject) {
return this.doneProcessing(rowData);
}
series = this.handle.getSeries(telemetryObject);
pointCount = series.getPointCount();
end = Math.min(start + this.batchSize, pointCount);
//Process rows in a batch with size not exceeding a maximum length
for (; i < end; i++) {
rowData.push(this.table.getRowValues(telemetryObject,
this.handle.makeDatum(telemetryObject, series, i)));
}
//Done processing all rows for this object.
if (end >= pointCount) {
offset++;
end = 0;
}
// Done processing either a batch or an object, yield process
// before continuing processing
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, objects, offset, end, rowData));
};
/**
* Populates historical data on scope when it becomes available from
* the telemetry API
*/
HistoricalTableController.prototype.addHistoricalData = function () {
if (this.timeoutHandle) {
this.$timeout.cancel(this.timeoutHandle);
}
this.timeoutHandle = this.$timeout(this.processTelemetryObjects.bind(this, this.handle.getTelemetryObjects(), 0, 0, []));
}; };
return HistoricalTableController; return HistoricalTableController;

View File

@ -91,6 +91,7 @@ define(
self.$scope.rows.length - 1); self.$scope.rows.length - 1);
} }
}); });
this.$scope.loading = false;
}; };
return RealtimeTableController; return RealtimeTableController;

View File

@ -83,16 +83,24 @@ define(
* @private * @private
*/ */
TelemetryTableController.prototype.registerChangeListeners = function () { TelemetryTableController.prototype.registerChangeListeners = function () {
var self = this;
this.unregisterChangeListeners(); this.unregisterChangeListeners();
// When composition changes, re-subscribe to the various // When composition changes, re-subscribe to the various
// telemetry subscriptions // telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection( this.changeListeners.push(this.$scope.$watchCollection(
'domainObject.getModel().composition', this.subscribe.bind(this))); 'domainObject.getModel().composition',
function (newVal, oldVal) {
if (newVal !== oldVal) {
self.subscribe();
}
})
);
//Change of bounds in time conductor //Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
this.subscribe.bind(this))); this.subscribe.bind(this))
);
}; };
/** /**
@ -132,6 +140,7 @@ define(
if (this.handle) { if (this.handle) {
this.handle.unsubscribe(); this.handle.unsubscribe();
} }
this.$scope.loading = true;
this.handle = this.$scope.domainObject && this.telemetryHandler.handle( this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject, this.$scope.domainObject,

View File

@ -34,6 +34,8 @@ define(
mockDomainObject, mockDomainObject,
mockTable, mockTable,
mockConfiguration, mockConfiguration,
mockAngularTimeout,
mockTimeoutHandle,
watches, watches,
controller; controller;
@ -63,6 +65,11 @@ define(
watches[expression] = callback; watches[expression] = callback;
}); });
mockTimeoutHandle = jasmine.createSpy("timeoutHandle");
mockAngularTimeout = jasmine.createSpy("$timeout");
mockAngularTimeout.andReturn(mockTimeoutHandle);
mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout");
mockConfiguration = { mockConfiguration = {
'range1': true, 'range1': true,
'range2': true, 'range2': true,
@ -107,7 +114,7 @@ define(
]); ]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle); mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter); controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, mockAngularTimeout);
controller.table = mockTable; controller.table = mockTable;
controller.handle = mockTelemetryHandle; controller.handle = mockTelemetryHandle;
}); });
@ -163,6 +170,13 @@ define(
controller.addHistoricalData(mockDomainObject, mockSeries); controller.addHistoricalData(mockDomainObject, mockSeries);
// Angular timeout is called a minumum of twice, regardless
// of batch size used.
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5); expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow); expect(controller.$scope.rows[0]).toBe(mockRow);
}); });
@ -198,7 +212,7 @@ define(
' object composition changes', function () { ' object composition changes', function () {
controller.registerChangeListeners(); controller.registerChangeListeners();
expect(watches['domainObject.getModel().composition']).toBeDefined(); expect(watches['domainObject.getModel().composition']).toBeDefined();
watches['domainObject.getModel().composition'](); watches['domainObject.getModel().composition']([], []);
expect(controller.subscribe).toHaveBeenCalled(); expect(controller.subscribe).toHaveBeenCalled();
}); });
@ -219,6 +233,78 @@ define(
}); });
}); });
describe('Yields thread', function () {
var mockSeries,
mockRow;
beforeEach(function () {
mockSeries = {
getPointCount: function () {
return 5;
},
getDomainValue: function () {
return 'Domain Value';
},
getRangeValue: function () {
return 'Range Value';
}
};
mockRow = {'domain': 'Domain Value', 'range': 'Range Value'};
mockTelemetryHandle.makeDatum.andCallFake(function () {
return mockRow;
});
mockTable.getRowValues.andReturn(mockRow);
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
mockTelemetryHandle.getSeries.andReturn(mockSeries);
});
it('when row count exceeds batch size', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Timeout is called a minimum of two times
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
expect(mockAngularTimeout.calls.length).toEqual(2);
mockAngularTimeout.mostRecentCall.args[0]();
//Because it yields, timeout will have been called a
// third time for the batch.
expect(mockAngularTimeout.calls.length).toEqual(3);
mockAngularTimeout.mostRecentCall.args[0]();
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
it('cancelling any outstanding timeouts', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout).toHaveBeenCalled();
mockAngularTimeout.mostRecentCall.args[0]();
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
it('cancels timeout on scope destruction', function () {
controller.batchSize = 3;
controller.addHistoricalData(mockDomainObject, mockSeries);
//Destroy is used by parent class as well, so multiple
// calls are made to scope.$on
var destroyCalls = mockScope.$on.calls.filter(function (call) {
return call.args[0] === '$destroy';
});
//Call destroy function
expect(destroyCalls.length).toEqual(2);
destroyCalls[0].args[1]();
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
});
});
}); });
} }
); );