diff --git a/example/generator/bundle.js b/example/generator/bundle.js index 259c5cff15..f1c0f83224 100644 --- a/example/generator/bundle.js +++ b/example/generator/bundle.js @@ -75,8 +75,7 @@ define([ }, { "key": "delta", - "name": "Delta", - "format": "example.delta" + "name": "Delta" } ], "priority": -1 @@ -103,11 +102,13 @@ define([ "domains": [ { "key": "utc", - "name": "Time" + "name": "Time", + "format": "utc" }, { "key": "yesterday", - "name": "Yesterday" + "name": "Yesterday", + "format": "utc" }, { "key": "delta", diff --git a/example/generator/src/generatorWorker.js b/example/generator/src/generatorWorker.js index bb4e55ca4b..091297e185 100644 --- a/example/generator/src/generatorWorker.js +++ b/example/generator/src/generatorWorker.js @@ -24,6 +24,7 @@ (function () { + var FIFTEEN_MINUTES = 15 * 60 * 1000; var handlers = { subscribe: onSubscribe, @@ -82,8 +83,11 @@ function onRequest(message) { var data = message.data; - if (!data.start || !data.end) { - throw new Error('missing start and end!'); + if (data.end == undefined) { + data.end = Date.now(); + } + if (data.start == undefined){ + data.start = data.end - FIFTEEN_MINUTES; } var now = Date.now(); diff --git a/platform/features/table/bundle.js b/platform/features/table/bundle.js index 02b78f847f..c034677a02 100644 --- a/platform/features/table/bundle.js +++ b/platform/features/table/bundle.js @@ -22,25 +22,21 @@ define([ "./src/directives/MCTTable", - "./src/controllers/RealtimeTableController", - "./src/controllers/HistoricalTableController", + "./src/controllers/TelemetryTableController", "./src/controllers/TableOptionsController", '../../commonUI/regions/src/Region', '../../commonUI/browse/src/InspectorRegion', "text!./res/templates/table-options-edit.html", - "text!./res/templates/rt-table.html", - "text!./res/templates/historical-table.html", + "text!./res/templates/telemetry-table.html", "legacyRegistry" ], function ( MCTTable, - RealtimeTableController, - HistoricalTableController, + TelemetryTableController, TableOptionsController, Region, InspectorRegion, tableOptionsEditTemplate, - rtTableTemplate, - historicalTableTemplate, + telemetryTableTemplate, legacyRegistry ) { /** @@ -65,9 +61,9 @@ define([ "types": [ { "key": "table", - "name": "Historical Telemetry Table", - "cssclass": "icon-tabular", - "description": "A static table of all values over time for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. The number of rows is based on the range of your query. New incoming data must be manually re-queried for.", + "name": "Telemetry Table", + "cssclass": "icon-tabular-realtime", + "description": "A table of values over a given time period. The table will be automatically updated with new values as they become available", "priority": 861, "features": "creation", "delegates": [ @@ -85,42 +81,13 @@ define([ "views": [ "table" ] - }, - { - "key": "rttable", - "name": "Real-time Telemetry Table", - "cssclass": "icon-tabular-realtime", - "description": "A scrolling table of latest values for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. New incoming data is automatically added to the view.", - "priority": 860, - "features": "creation", - "delegates": [ - "telemetry" - ], - "inspector": tableInspector, - "contains": [ - { - "has": "telemetry" - } - ], - "model": { - "composition": [] - }, - "views": [ - "rt-table", - "scrolling-table" - ] } ], "controllers": [ { - "key": "HistoricalTableController", - "implementation": HistoricalTableController, - "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "$timeout", "openmct"] - }, - { - "key": "RealtimeTableController", - "implementation": RealtimeTableController, - "depends": ["$scope", "telemetryHandler", "telemetryFormatter", "openmct"] + "key": "TelemetryTableController", + "implementation": TelemetryTableController, + "depends": ["$scope", "openmct"] }, { "key": "TableOptionsController", @@ -131,21 +98,10 @@ define([ ], "views": [ { - "name": "Historical Table", + "name": "Telemetry Table", "key": "table", - "template": historicalTableTemplate, - "cssclass": "icon-tabular", - "needs": [ - "telemetry" - ], - "delegation": true, - "editable": false - }, - { - "name": "Real-time Table", - "key": "rt-table", "cssclass": "icon-tabular-realtime", - "template": rtTableTemplate, + "template": telemetryTableTemplate, "needs": [ "telemetry" ], diff --git a/platform/features/table/res/templates/rt-table.html b/platform/features/table/res/templates/rt-table.html deleted file mode 100644 index da08b0ee8e..0000000000 --- a/platform/features/table/res/templates/rt-table.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - -
\ No newline at end of file diff --git a/platform/features/table/res/templates/historical-table.html b/platform/features/table/res/templates/telemetry-table.html similarity index 82% rename from platform/features/table/res/templates/historical-table.html rename to platform/features/table/res/templates/telemetry-table.html index c2abbf5708..6dae139263 100644 --- a/platform/features/table/res/templates/historical-table.html +++ b/platform/features/table/res/templates/telemetry-table.html @@ -1,4 +1,4 @@ -
0) { - self.addColumn(new NameColumn(), 0); - } } return this; }; @@ -99,9 +102,8 @@ define( * @returns {Array} The titles of the columns */ TableConfiguration.prototype.getHeaders = function () { - var self = this; return this.columns.map(function (column, i) { - return self.getColumnTitle(column) || 'Column ' + (i + 1); + return column.getTitle()|| 'Column ' + (i + 1); }); }; @@ -113,11 +115,11 @@ define( * @returns {Object} Key value pairs where the key is the column * title, and the value is the formatted value from the provided datum. */ - TableConfiguration.prototype.getRowValues = function (telemetryObject, datum) { + TableConfiguration.prototype.getRowValues = function (limitEvaluator, datum) { var self = this; return this.columns.reduce(function (rowObject, column, i) { var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1), - columnValue = column.getValue(telemetryObject, datum); + columnValue = column.getValue(datum, limitEvaluator); if (columnValue !== undefined && columnValue.text === undefined) { columnValue.text = ''; diff --git a/platform/features/table/src/controllers/HistoricalTableController.js b/platform/features/table/src/controllers/HistoricalTableController.js deleted file mode 100644 index 0f56f6b4ee..0000000000 --- a/platform/features/table/src/controllers/HistoricalTableController.js +++ /dev/null @@ -1,141 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2016, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - './TelemetryTableController' - ], - function (TableController) { - var BATCH_SIZE = 1000; - - /** - * Extends TelemetryTableController and adds real-time streaming - * support. - * @memberof platform/features/table - * @param $scope - * @param telemetryHandler - * @param telemetryFormatter - * @constructor - */ - function HistoricalTableController($scope, telemetryHandler, telemetryFormatter, $timeout, openmct) { - 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, openmct); - } - - HistoricalTableController.prototype = Object.create(TableController.prototype); - - /** - * Set provided row data on scope, and cancel loading spinner - * @private - */ - HistoricalTableController.prototype.doneProcessing = function (rowData) { - this.$scope.rows = rowData; - this.$scope.loading = false; - }; - - /** - * @private - */ - HistoricalTableController.prototype.registerChangeListeners = function () { - TableController.prototype.registerChangeListeners.call(this); - //Change of bounds in time conductor - this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', - this.boundsChange.bind(this)) - ); - }; - - /** - * @private - */ - HistoricalTableController.prototype.boundsChange = function (event, bounds, follow) { - // If in follow mode, don't bother re-subscribing, data will be - // received from existing subscription. - if (follow !== true) { - this.subscribe(); - } - }; - - /** - * 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; - } -); diff --git a/platform/features/table/src/controllers/RealtimeTableController.js b/platform/features/table/src/controllers/RealtimeTableController.js deleted file mode 100644 index c6ff7b8aee..0000000000 --- a/platform/features/table/src/controllers/RealtimeTableController.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2016, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - './TelemetryTableController' - ], - function (TableController) { - - /** - * Extends TelemetryTableController and adds real-time streaming - * support. - * @memberof platform/features/table - * @param $scope - * @param telemetryHandler - * @param telemetryFormatter - * @constructor - */ - function RealtimeTableController($scope, telemetryHandler, telemetryFormatter, openmct) { - TableController.call(this, $scope, telemetryHandler, telemetryFormatter, openmct); - - this.maxRows = 100000; - } - - RealtimeTableController.prototype = Object.create(TableController.prototype); - - /** - * Overrides method on TelemetryTableController providing handling - * for realtime data. - */ - 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); - - //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.$scope.loading = false; - }; - - return RealtimeTableController; - } -); diff --git a/platform/features/table/src/controllers/TelemetryTableController.js b/platform/features/table/src/controllers/TelemetryTableController.js index d845c13d2f..8eea6887dc 100644 --- a/platform/features/table/src/controllers/TelemetryTableController.js +++ b/platform/features/table/src/controllers/TelemetryTableController.js @@ -38,14 +38,10 @@ define( * configuration, and telemetry subscriptions. * @memberof platform/features/table * @param $scope - * @param telemetryHandler - * @param telemetryFormatter * @constructor */ function TelemetryTableController( $scope, - telemetryHandler, - telemetryFormatter, openmct ) { var self = this; @@ -53,9 +49,8 @@ define( this.$scope = $scope; this.columns = {}; //Range and Domain columns this.handle = undefined; - this.telemetryHandler = telemetryHandler; this.table = new TableConfiguration($scope.domainObject, - telemetryFormatter); + openmct); this.changeListeners = []; this.conductor = openmct.conductor; this.openmct = openmct; @@ -68,6 +63,9 @@ define( self.subscribe(); self.registerChangeListeners(); }); + this.mutationListener = openmct.objects.observe(this.newObject, "*", function (domainObject){ + self.newObject = domainObject; + }); this.destroy = this.destroy.bind(this); @@ -79,6 +77,8 @@ define( this.sortByTimeSystem = this.sortByTimeSystem.bind(this); this.conductor.on('timeSystem', this.sortByTimeSystem); this.conductor.off('timeSystem', this.sortByTimeSystem); + + this.subscriptions = []; } /** @@ -130,29 +130,12 @@ define( * Release the current subscription (called when scope is destroyed) */ TelemetryTableController.prototype.destroy = function () { - if (this.handle) { - this.handle.unsubscribe(); - this.handle = undefined; - } + this.subscriptions.forEach(function (subscription) { + subscription() + }); + this.mutationListener(); }; - /** - * 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 @@ -160,94 +143,123 @@ define( only). */ TelemetryTableController.prototype.subscribe = function () { + var self = this; var telemetryApi = this.openmct.telemetry; + var compositionApi = this.openmct.composition; + var subscriptions = this.subscriptions; + var tableConfiguration = this.table; + var scope = this.$scope; + var maxRows = 100000; + var conductor = this.conductor; + var newObject = this.newObject; - if (this.handle) { - this.handle.unsubscribe(); - } this.$scope.loading = true; - function map(func){ - return function (objects) { - return Promise.all(objects.map(func)); + function makeTableRows(object, historicalData){ + var limitEvaluator = telemetryApi.limitEvaluator(object); + return historicalData.map(tableConfiguration.getRowValues.bind(tableConfiguration, limitEvaluator)); + } + + function requestData(objects) { + var bounds = conductor.bounds(); + + return Promise.all( + objects.map(function (object) { + return telemetryApi.request(object, { + start: bounds.start, + end: bounds.end + }).then( + makeTableRows.bind(this, object) + ); + }) + ); + } + + function addHistoricalData(historicalData){ + scope.rows = Array.prototype.concat.apply([], historicalData); + scope.loading = false; + } + + function newData(domainObject, datum) { + scope.rows.push(tableConfiguration.getRowValues(datum, telemetryApi.limitEvaluator(domainObject))); + + //Inform table that a new row has been added + if (scope.rows.length > maxRows) { + scope.$broadcast('remove:row', 0); + scope.rows.shift(); } + + scope.$broadcast('add:row', + scope.rows.length - 1); + } - function add(object){ - return function (objects) { - objects.unshift(object); - return objects; - } + function subscribe(objects) { + objects.forEach(function (object){ + subscriptions.push(telemetryApi.subscribe(object, newData.bind(this, object), {})); + }); + return objects; } - function subscribeTo(object) { - return telemetryApi.request(object, {}); + function error(e) { + throw e; } - function error() { - console.log("Unable to subscribe"); + function loadColumns(objects) { + var metadatas = objects.map(telemetryApi.getMetadata.bind(telemetryApi)); + var allColumns = telemetryApi.commonValuesForHints(metadatas, []); + + tableConfiguration.populateColumns(allColumns); + + this.timeColumns = telemetryApi.commonValuesForHints(metadatas, ['x']).map(function (metadatum){ + return metadatum.name; + }); + + self.filterColumns(); + + return Promise.resolve(objects); } - this.openmct.composition.get(this.newObject) - .load() - .then(add(this.newObject)) - .then(map(subscribeTo)) - .then(function (telemetry) { - console.log(telemetry.length); - }).catch(error); - - this.handle = this.$scope.domainObject && this.telemetryHandler.handle( - this.$scope.domainObject, - this.addRealtimeData.bind(this), - true // Lossless - ); - - this.handle.request({}).then(this.addHistoricalData.bind(this)); - - this.setup(); - }; - - TelemetryTableController.prototype.populateColumns = function (telemetryMetadata) { - this.table.populateColumns(telemetryMetadata); - - //Identify time columns - telemetryMetadata.forEach(function (metadatum) { - //Push domains first - (metadatum.domains || []).forEach(function (domainMetadata) { - this.timeColumns.push(domainMetadata.name); - }.bind(this)); - }.bind(this)); - - var timeSystem = this.conductor.timeSystem(); - if (timeSystem) { - this.sortByTimeSystem(timeSystem); + function filterForTelemetry(objects){ + return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi)); } - }; - /** - * Setup table columns based on domain object metadata - */ - TelemetryTableController.prototype.setup = function () { - var handle = this.handle, - self = this; + function getDomainObjects() { + return new Promise(function (resolve, reject){ + var objects = [newObject]; + var composition = compositionApi.get(newObject); - if (handle) { - this.timeColumns = []; - handle.promiseTelemetryObjects().then(function () { - self.$scope.headers = []; - self.$scope.rows = []; - - self.populateColumns(handle.getMetadata()); - self.filterColumns(); - - // When table column configuration changes, (due to being - // selected or deselected), filter columns appropriately. - self.changeListeners.push(self.$scope.$watchCollection( - 'domainObject.getModel().configuration.table.columns', - self.filterColumns.bind(self) - )); + if (composition) { + composition + .load() + .then(function (children) { + return objects.concat(children); + }) + .then(resolve) + .catch(reject); + } else { + return resolve(objects); + } }); } + + scope.headers = []; + scope.rows = []; + + getDomainObjects() + .then(filterForTelemetry) + .catch(error) + .then(function (objects){ + if (objects.length > 0){ + return loadColumns(objects) + .then(subscribe) + .then(requestData) + .then(addHistoricalData) + .catch(error); + } else { + scope.loading = false; + } + }) }; /**