From 99ba9edb95ffb8778a73bfbf2a4bfb71feed4ea6 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 11 Apr 2016 12:42:15 -0700 Subject: [PATCH] Merged Resolved merge conflicts Resolved merge conflicts --- platform/features/table/bundle.js | 18 ++-- .../{table.html => historical-table.html} | 2 +- .../table/res/templates/rt-table.html | 2 +- .../features/table/src/TableConfiguration.js | 25 ++++- .../controllers/HistoricalTableController.js | 70 ++++++++++++ .../src/controllers/MCTTableController.js | 101 +++++++++--------- ...ntroller.js => RealtimeTableController.js} | 71 ++++-------- .../src/controllers/TableOptionsController.js | 24 ++++- .../controllers/TelemetryTableController.js | 94 +++++++--------- .../table/test/TableConfigurationSpec.js | 11 +- ...ec.js => HistoricalTableControllerSpec.js} | 17 ++- .../controllers/MCTTableControllerSpec.js | 24 ++--- ...Spec.js => RealtimeTableControllerSpec.js} | 13 ++- .../controllers/TableOptionsControllerSpec.js | 10 +- 14 files changed, 273 insertions(+), 209 deletions(-) rename platform/features/table/res/templates/{table.html => historical-table.html} (71%) create mode 100644 platform/features/table/src/controllers/HistoricalTableController.js rename platform/features/table/src/controllers/{RTTelemetryTableController.js => RealtimeTableController.js} (58%) rename platform/features/table/test/controllers/{TelemetryTableControllerSpec.js => HistoricalTableControllerSpec.js} (94%) rename platform/features/table/test/controllers/{RTTelemetryTableControllerSpec.js => RealtimeTableControllerSpec.js} (94%) diff --git a/platform/features/table/bundle.js b/platform/features/table/bundle.js index 15517c115c..0220b7dc6d 100644 --- a/platform/features/table/bundle.js +++ b/platform/features/table/bundle.js @@ -23,16 +23,16 @@ define([ "./src/directives/MCTTable", - "./src/controllers/RTTelemetryTableController", - "./src/controllers/TelemetryTableController", + "./src/controllers/RealtimeTableController", + "./src/controllers/HistoricalTableController", "./src/controllers/TableOptionsController", '../../commonUI/regions/src/Region', '../../commonUI/browse/src/InspectorRegion', "legacyRegistry" ], function ( MCTTable, - RTTelemetryTableController, - TelemetryTableController, + RealtimeTableController, + HistoricalTableController, TableOptionsController, Region, InspectorRegion, @@ -109,13 +109,13 @@ define([ ], "controllers": [ { - "key": "TelemetryTableController", - "implementation": TelemetryTableController, + "key": "HistoricalTableController", + "implementation": HistoricalTableController, "depends": ["$scope", "telemetryHandler", "telemetryFormatter"] }, { - "key": "RTTelemetryTableController", - "implementation": RTTelemetryTableController, + "key": "RealtimeTableController", + "implementation": RealtimeTableController, "depends": ["$scope", "telemetryHandler", "telemetryFormatter"] }, { @@ -130,7 +130,7 @@ define([ "name": "Historical Table", "key": "table", "glyph": "\ue604", - "templateUrl": "templates/table.html", + "templateUrl": "templates/historical-table.html", "needs": [ "telemetry" ], diff --git a/platform/features/table/res/templates/table.html b/platform/features/table/res/templates/historical-table.html similarity index 71% rename from platform/features/table/res/templates/table.html rename to platform/features/table/res/templates/historical-table.html index c63f6d63fc..9917c41dcc 100644 --- a/platform/features/table/res/templates/table.html +++ b/platform/features/table/res/templates/historical-table.html @@ -1,4 +1,4 @@ -
+
+
0) { this.$scope.totalWidth = tableWidth + 'px'; @@ -439,7 +434,6 @@ define( prevLargest[key] = JSON.parse(JSON.stringify(row[key])); } } - }); return prevLargest; }, JSON.parse(JSON.stringify(rows[0] || {}))); @@ -447,26 +441,30 @@ define( }; /** - * Calculates the widest row in the table, pads that row, and adds - * it to the table. Allows the table to size itself, then uses this - * as basis for column dimensions. + * Calculates the widest row in the table, and if necessary, resizes + * the table accordingly + * + * @param rows the rows on which to resize + * @returns {Promise} a promise that will resolve when resizing has + * occurred. * @private */ - MCTTableController.prototype.resize = function (){ - var self = this; + MCTTableController.prototype.resize = function (rows){ + //Calculate largest row + var largestRow = this.buildLargestRow(rows); - this.$scope.sizingRow = this.buildLargestRow(this.$scope.displayRows); - - //Wait a timeout to allow digest of previous change to visible - // rows to happen. - this.$timeout(function () { - self.$scope.visibleRows = []; - self.setElementSizes(); - }); + // Has it changed? If so, set the the 'sizing' row which + // determines column widths + if (JSON.stringify(largestRow) !== JSON.stringify(this.$scope.sizingRow)){ + this.$scope.sizingRow = largestRow; + return this.$timeout(this.setElementSizes.bind(this)); + } else { + return fastPromise(undefined); + } }; /** - * @priate + * @private */ MCTTableController.prototype.filterAndSort = function (rows) { var displayRows = rows; @@ -484,17 +482,14 @@ define( * Update rows with new data. If filtering is enabled, rows * will be sorted before display. */ - MCTTableController.prototype.updateRows = function (newRows) { - //Reset visible rows because new row data available. - this.$scope.visibleRows = []; - + MCTTableController.prototype.setRows = function (newRows) { //Nothing to show because no columns visible - if (!this.$scope.displayHeaders) { + if (!this.$scope.displayHeaders || !newRows) { return; } this.filterAndSort(newRows || []); - this.resize(); + this.resize(newRows).then(this.setVisibleRows.bind(this)); }; /** diff --git a/platform/features/table/src/controllers/RTTelemetryTableController.js b/platform/features/table/src/controllers/RealtimeTableController.js similarity index 58% rename from platform/features/table/src/controllers/RTTelemetryTableController.js rename to platform/features/table/src/controllers/RealtimeTableController.js index 8a61d61b5e..cf58cb236e 100644 --- a/platform/features/table/src/controllers/RTTelemetryTableController.js +++ b/platform/features/table/src/controllers/RealtimeTableController.js @@ -37,7 +37,7 @@ define( * @param telemetryFormatter * @constructor */ - function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) { + function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) { TableController.call(this, $scope, telemetryHandler, telemetryFormatter); $scope.autoScroll = false; @@ -66,58 +66,31 @@ define( }); } - RTTelemetryTableController.prototype = Object.create(TableController.prototype); + RealtimeTableController.prototype = Object.create(TableController.prototype); - /** - Override the subscribe function defined on the parent controller in - order to handle realtime telemetry instead of historical. - */ - RTTelemetryTableController.prototype.subscribe = function () { - var self = this; - self.$scope.rows = undefined; - (this.subscriptions || []).forEach(function (unsubscribe){ - unsubscribe(); - }); + RealtimeTableController.prototype.addRealtimeData = function() { + var self = this, + datum, + row; + this.handle.getTelemetryObjects().forEach(function (telemetryObject){ + datum = self.handle.getDatum(telemetryObject); + if (datum) { + //Populate row values from telemetry datum + row = self.table.getRowValues(telemetryObject, datum); + self.$scope.rows.push(row); - if (this.handle) { - this.handle.unsubscribe(); - } - - function updateData(){ - var datum, - row; - self.handle.getTelemetryObjects().forEach(function (telemetryObject){ - datum = self.handle.getDatum(telemetryObject); - if (datum) { - row = self.table.getRowValues(telemetryObject, datum); - if (!self.$scope.rows){ - self.$scope.rows = [row]; - self.$scope.$digest(); - } else { - self.$scope.rows.push(row); - - if (self.$scope.rows.length > self.maxRows) { - self.$scope.$broadcast('remove:row', 0); - self.$scope.rows.shift(); - } - - self.$scope.$broadcast('add:row', - self.$scope.rows.length - 1); - } + //Inform table that a new row has been added + if (self.$scope.rows.length > self.maxRows) { + self.$scope.$broadcast('remove:row', 0); + self.$scope.rows.shift(); } - }); - } + self.$scope.$broadcast('add:row', + self.$scope.rows.length - 1); + } + }); + } - this.handle = this.$scope.domainObject && this.telemetryHandler.handle( - this.$scope.domainObject, - updateData, - true // Lossless - ); - - this.setup(); - }; - - return RTTelemetryTableController; + return RealtimeTableController; } ); diff --git a/platform/features/table/src/controllers/TableOptionsController.js b/platform/features/table/src/controllers/TableOptionsController.js index c3b479073c..499e91efbc 100644 --- a/platform/features/table/src/controllers/TableOptionsController.js +++ b/platform/features/table/src/controllers/TableOptionsController.js @@ -51,13 +51,22 @@ define( this.$scope = $scope; this.domainObject = $scope.domainObject; + this.listeners = []; $scope.columnsForm = {}; - this.domainObject.getCapability('mutation').listen(function (model) { - self.populateForm(model); + $scope.$watch('domainObject', function(domainObject) { + self.populateForm(domainObject.getModel()); + + self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) { + self.populateForm(model); + })); }); + /** + * Maintain a configuration object on scope that stores column + * configuration. On change, synchronize with object model. + */ $scope.$watchCollection('configuration.table.columns', function (columns){ if (columns){ self.domainObject.useCapability('mutation', function (model) { @@ -67,6 +76,15 @@ define( } }); + /** + * Destroy all mutation listeners + */ + $scope.$on('$destroy', function () { + self.listeners.forEach(function (listener) { + listener(); + }); + }) + } TableOptionsController.prototype.populateForm = function (model) { @@ -86,7 +104,7 @@ define( 'key': key }); }); - this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration)); + this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {})); }; return TableOptionsController; diff --git a/platform/features/table/src/controllers/TelemetryTableController.js b/platform/features/table/src/controllers/TelemetryTableController.js index e579c5eeb8..b41cb940f5 100644 --- a/platform/features/table/src/controllers/TelemetryTableController.js +++ b/platform/features/table/src/controllers/TelemetryTableController.js @@ -52,19 +52,15 @@ define( this.$scope = $scope; this.columns = {}; //Range and Domain columns this.handle = undefined; - //this.pending = false; this.telemetryHandler = telemetryHandler; this.table = new TableConfiguration($scope.domainObject, telemetryFormatter); this.changeListeners = []; - $scope.rows = undefined; + $scope.rows = []; // Subscribe to telemetry when a domain object becomes available - this.$scope.$watch('domainObject', function(domainObject){ - if (!domainObject) - return; - + this.$scope.$watch('domainObject', function(){ self.subscribe(); self.registerChangeListeners(); }); @@ -73,16 +69,24 @@ define( this.$scope.$on("$destroy", this.destroy.bind(this)); } + /** + * @private + */ + TelemetryTableController.prototype.unregisterChangeListeners = function () { + this.changeListeners.forEach(function (listener) { + return listener && listener(); + }); + this.changeListeners = []; + } + /** * Defer registration of change listeners until domain object is * available in order to avoid race conditions * @private */ TelemetryTableController.prototype.registerChangeListeners = function () { - this.changeListeners.forEach(function (listener) { - return listener && listener(); - }); - this.changeListeners = []; + this.unregisterChangeListeners(); + // When composition changes, re-subscribe to the various // telemetry subscriptions this.changeListeners.push(this.$scope.$watchCollection( @@ -103,25 +107,37 @@ define( } }; + /** + * Function for handling realtime data when it is available. This + * will be called by the telemetry framework when new data is + * available. + * + * Method should be overridden by specializing class. + */ + TelemetryTableController.prototype.addRealtimeData = function () { + }; + + /** + * Function for handling historical data. Will be called by + * telemetry framework when requested historical data is available. + * Should be overridden by specializing class. + */ + TelemetryTableController.prototype.addHistoricalData = function () { + }; + /** Create a new subscription. This can be overridden by children to change default behaviour (which is to retrieve historical telemetry only). */ TelemetryTableController.prototype.subscribe = function () { - var self = this; - if (this.handle) { this.handle.unsubscribe(); } - //Noop because not supporting realtime data right now - function noop(){ - } - this.handle = this.$scope.domainObject && this.telemetryHandler.handle( this.$scope.domainObject, - noop, + this.addRealtimeData.bind(this), true // Lossless ); @@ -130,28 +146,6 @@ define( this.setup(); }; - /** - * Populates historical data on scope when it becomes available - * @private - */ - TelemetryTableController.prototype.addHistoricalData = function () { - 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; - }; - /** * Setup table columns based on domain object metadata */ @@ -162,7 +156,9 @@ define( if (handle) { handle.promiseTelemetryObjects().then(function () { - table.buildColumns(handle.getMetadata()); + self.$scope.headers = [] + self.$scope.rows = []; + table.populateColumns(handle.getMetadata()); self.filterColumns(); @@ -176,26 +172,14 @@ define( } }; - /** - * @private - * @param object The object for which data is available (table may - * be composed of multiple objects) - * @param datum The data received from the telemetry source - */ - TelemetryTableController.prototype.updateRows = function (object, datum) { - this.$scope.rows.push(this.table.getRowValues(object, datum)); - }; - /** * When column configuration changes, update the visible headers * accordingly. * @private */ - TelemetryTableController.prototype.filterColumns = function (columnConfig) { - if (!columnConfig){ - columnConfig = this.table.getColumnConfiguration(); - this.table.saveColumnConfiguration(columnConfig); - } + TelemetryTableController.prototype.filterColumns = function () { + var columnConfig = this.table.buildColumnConfiguration(); + //Populate headers with visible columns (determined by configuration) this.$scope.headers = Object.keys(columnConfig).filter(function (column) { return columnConfig[column]; diff --git a/platform/features/table/test/TableConfigurationSpec.js b/platform/features/table/test/TableConfigurationSpec.js index 86a18aee5a..79583d09f8 100644 --- a/platform/features/table/test/TableConfigurationSpec.js +++ b/platform/features/table/test/TableConfigurationSpec.js @@ -116,10 +116,10 @@ define( }]; beforeEach(function() { - table.buildColumns(metadata); + table.populateColumns(metadata); }); - it("populates the columns attribute", function() { + it("populates columns", function() { expect(table.columns.length).toBe(5); }); @@ -141,7 +141,7 @@ define( it("Provides a default configuration with all columns" + " visible", function() { - var configuration = table.getColumnConfiguration(); + var configuration = table.buildColumnConfiguration(); expect(configuration).toBeDefined(); expect(Object.keys(configuration).every(function(key){ @@ -160,7 +160,7 @@ define( }; mockModel.configuration = modelConfig; - tableConfig = table.getColumnConfiguration(); + tableConfig = table.buildColumnConfiguration(); expect(tableConfig).toBeDefined(); expect(tableConfig['Range 1']).toBe(false); @@ -191,6 +191,9 @@ define( expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled(); }); }); + /** + * TODO: Add test for saving column config + */ }); }); } diff --git a/platform/features/table/test/controllers/TelemetryTableControllerSpec.js b/platform/features/table/test/controllers/HistoricalTableControllerSpec.js similarity index 94% rename from platform/features/table/test/controllers/TelemetryTableControllerSpec.js rename to platform/features/table/test/controllers/HistoricalTableControllerSpec.js index 03f62f11e3..f000529467 100644 --- a/platform/features/table/test/controllers/TelemetryTableControllerSpec.js +++ b/platform/features/table/test/controllers/HistoricalTableControllerSpec.js @@ -23,7 +23,7 @@ define( [ - "../../src/controllers/TelemetryTableController" + "../../src/controllers/HistoricalTableController" ], function (TableController) { "use strict"; @@ -73,14 +73,14 @@ define( mockTable = jasmine.createSpyObj('table', [ - 'buildColumns', - 'getColumnConfiguration', + 'populateColumns', + 'buildColumnConfiguration', 'getRowValues', 'saveColumnConfiguration' ] ); mockTable.columns = []; - mockTable.getColumnConfiguration.andReturn(mockConfiguration); + mockTable.buildColumnConfiguration.andReturn(mockConfiguration); mockDomainObject= jasmine.createSpyObj('domainObject', [ 'getCapability', @@ -126,21 +126,18 @@ define( expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled(); }); - describe('the controller makes use of the table', function () { + describe('makes use of the table', function () { it('to create column definitions from telemetry' + ' metadata', function () { controller.setup(); - expect(mockTable.buildColumns).toHaveBeenCalled(); + expect(mockTable.populateColumns).toHaveBeenCalled(); }); it('to create column configuration, which is written to the' + ' object model', function () { - var mockModel = {}; - controller.setup(); - expect(mockTable.getColumnConfiguration).toHaveBeenCalled(); - expect(mockTable.saveColumnConfiguration).toHaveBeenCalled(); + expect(mockTable.buildColumnConfiguration).toHaveBeenCalled(); }); }); diff --git a/platform/features/table/test/controllers/MCTTableControllerSpec.js b/platform/features/table/test/controllers/MCTTableControllerSpec.js index 5ee9f21c41..7bca5c65b4 100644 --- a/platform/features/table/test/controllers/MCTTableControllerSpec.js +++ b/platform/features/table/test/controllers/MCTTableControllerSpec.js @@ -118,7 +118,7 @@ define( }); it('Sets rows on scope when rows change', function() { - controller.updateRows(testRows); + controller.setRows(testRows); expect(mockScope.displayRows.length).toBe(3); expect(mockScope.displayRows).toEqual(testRows); }); @@ -130,7 +130,7 @@ define( 'col2': {'text': 'ghi'}, 'col3': {'text': 'row3 col3'} }; - controller.updateRows(testRows); + controller.setRows(testRows); expect(mockScope.displayRows.length).toBe(3); testRows.push(row4); addRowFunc(undefined, 3); @@ -139,7 +139,7 @@ define( it('Supports removing rows individually', function() { var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1]; - controller.updateRows(testRows); + controller.setRows(testRows); expect(mockScope.displayRows.length).toBe(3); removeRowFunc(undefined, 2); expect(mockScope.displayRows.length).toBe(2); @@ -211,20 +211,20 @@ define( mockScope.displayRows = controller.sortRows(testRows.slice(0)); mockScope.rows.push(row4); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[0].col2.text).toEqual('xyz'); mockScope.rows.push(row5); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[4].col2.text).toEqual('aaa'); mockScope.rows.push(row6); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[2].col2.text).toEqual('ggg'); //Add a duplicate row mockScope.rows.push(row6); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[2].col2.text).toEqual('ggg'); expect(mockScope.displayRows[3].col2.text).toEqual('ggg'); }); @@ -240,12 +240,12 @@ define( mockScope.displayRows = controller.filterRows(testRows); mockScope.rows.push(row5); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows.length).toBe(2); expect(mockScope.displayRows[1].col2.text).toEqual('aaa'); mockScope.rows.push(row6); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows.length).toBe(2); //Row was not added because does not match filter }); @@ -259,11 +259,11 @@ define( mockScope.displayRows = testRows.slice(0); mockScope.rows.push(row5); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[3].col2.text).toEqual('aaa'); mockScope.rows.push(row6); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(mockScope.displayRows[4].col2.text).toEqual('ggg'); }); @@ -282,7 +282,7 @@ define( mockScope.displayRows = testRows.slice(0); mockScope.rows.push(row7); - controller.newRow(undefined, mockScope.rows.length-1); + controller.addRow(undefined, mockScope.rows.length-1); expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'}); }); diff --git a/platform/features/table/test/controllers/RTTelemetryTableControllerSpec.js b/platform/features/table/test/controllers/RealtimeTableControllerSpec.js similarity index 94% rename from platform/features/table/test/controllers/RTTelemetryTableControllerSpec.js rename to platform/features/table/test/controllers/RealtimeTableControllerSpec.js index 59911d1771..e0b6978d88 100644 --- a/platform/features/table/test/controllers/RTTelemetryTableControllerSpec.js +++ b/platform/features/table/test/controllers/RealtimeTableControllerSpec.js @@ -23,7 +23,7 @@ define( [ - "../../src/controllers/RTTelemetryTableController" + "../../src/controllers/RealtimeTableController" ], function (TableController) { "use strict"; @@ -77,14 +77,14 @@ define( mockTable = jasmine.createSpyObj('table', [ - 'buildColumns', - 'getColumnConfiguration', + 'populateColumns', + 'buildColumnConfiguration', 'getRowValues', 'saveColumnConfiguration' ] ); mockTable.columns = []; - mockTable.getColumnConfiguration.andReturn(mockConfiguration); + mockTable.buildColumnConfiguration.andReturn(mockConfiguration); mockTable.getRowValues.andReturn(mockTableRow); mockDomainObject= jasmine.createSpyObj('domainObject', [ @@ -107,13 +107,16 @@ define( 'unsubscribe', 'getDatum', 'promiseTelemetryObjects', - 'getTelemetryObjects' + 'getTelemetryObjects', + 'request' ]); + // Arbitrary array with non-zero length, contents are not // used by mocks mockTelemetryHandle.getTelemetryObjects.andReturn([{}]); mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined)); mockTelemetryHandle.getDatum.andReturn({}); + mockTelemetryHandle.request.andReturn(promise(undefined)); mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [ 'handle' diff --git a/platform/features/table/test/controllers/TableOptionsControllerSpec.js b/platform/features/table/test/controllers/TableOptionsControllerSpec.js index 9de96b5f52..483696e0e2 100644 --- a/platform/features/table/test/controllers/TableOptionsControllerSpec.js +++ b/platform/features/table/test/controllers/TableOptionsControllerSpec.js @@ -47,11 +47,16 @@ define( 'listen' ]); mockDomainObject = jasmine.createSpyObj('domainObject', [ - 'getCapability' + 'getCapability', + 'getModel' ]); mockDomainObject.getCapability.andReturn(mockCapability); + mockDomainObject.getModel.andReturn({}); + mockScope = jasmine.createSpyObj('scope', [ - '$watchCollection' + '$watchCollection', + '$watch', + '$on' ]); mockScope.domainObject = mockDomainObject; @@ -59,6 +64,7 @@ define( }); it('Registers a listener for mutation events on the object', function() { + mockScope.$watch.mostRecentCall.args[1](mockDomainObject); expect(mockCapability.listen).toHaveBeenCalled(); });