diff --git a/example/generator/SinewaveLimitProvider.js b/example/generator/SinewaveLimitProvider.js
index 2cef29448f..22bb6ccd6d 100644
--- a/example/generator/SinewaveLimitProvider.js
+++ b/example/generator/SinewaveLimitProvider.js
@@ -27,8 +27,14 @@ define([
) {
- var RED = 0.9,
- YELLOW = 0.5,
+ var RED = {
+ sin: 0.9,
+ cos: 0.9
+ },
+ YELLOW = {
+ sin: 0.5,
+ cos: 0.5
+ },
LIMITS = {
rh: {
cssClass: "s-limit-upr s-limit-red",
@@ -67,17 +73,18 @@ define([
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
- var range = valueMetadata ? valueMetadata.key : 'sin'
- if (datum[range] > RED) {
+ var range = valueMetadata && valueMetadata.key;
+
+ if (datum[range] > RED[range]) {
return LIMITS.rh;
}
- if (datum[range] < -RED) {
+ if (datum[range] < -RED[range]) {
return LIMITS.rl;
}
- if (datum[range] > YELLOW) {
+ if (datum[range] > YELLOW[range]) {
return LIMITS.yh;
}
- if (datum[range] < -YELLOW) {
+ if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
}
diff --git a/openmct.js b/openmct.js
index 1e98ace22d..92202bab1f 100644
--- a/openmct.js
+++ b/openmct.js
@@ -38,6 +38,7 @@ var openmct = new MCT();
openmct.legacyRegistry = defaultRegistry;
openmct.install(openmct.plugins.Plot());
+openmct.install(openmct.plugins.TelemetryTable());
if (typeof BUILD_CONSTANTS !== 'undefined') {
openmct.install(buildInfo(BUILD_CONSTANTS));
diff --git a/platform/features/table/bundle.js b/platform/features/table/bundle.js
deleted file mode 100644
index 7e4c37ec75..0000000000
--- a/platform/features/table/bundle.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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([
- "./src/directives/MCTTable",
- "./src/controllers/TelemetryTableController",
- "./src/controllers/TableOptionsController",
- '../../commonUI/regions/src/Region',
- '../../commonUI/browse/src/InspectorRegion',
- "./res/templates/table-options-edit.html",
- "./res/templates/telemetry-table.html",
- "legacyRegistry"
-], function (
- MCTTable,
- TelemetryTableController,
- TableOptionsController,
- Region,
- InspectorRegion,
- tableOptionsEditTemplate,
- telemetryTableTemplate,
- legacyRegistry
-) {
- /**
- * Two region parts are defined here. One that appears only in browse
- * mode, and one that appears only in edit mode. For not they both point
- * to the same representation, but a different key could be used here to
- * include a customized representation for edit mode.
- */
- var tableInspector = new InspectorRegion(),
- tableOptionsEditRegion = new Region({
- name: "table-options",
- title: "Table Options",
- modes: ['edit'],
- content: {
- key: "table-options-edit"
- }
- });
- tableInspector.addRegion(tableOptionsEditRegion);
-
- legacyRegistry.register("platform/features/table", {
- "extensions": {
- "types": [
- {
- "key": "table",
- "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": [
- "telemetry"
- ],
- "inspector": "table-options-edit",
- "contains": [
- {
- "has": "telemetry"
- }
- ],
- "model": {
- "composition": []
- },
- "views": [
- "table"
- ]
- }
- ],
- "controllers": [
- {
- "key": "TelemetryTableController",
- "implementation": TelemetryTableController,
- "depends": ["$scope", "$timeout", "openmct"]
- },
- {
- "key": "TableOptionsController",
- "implementation": TableOptionsController,
- "depends": ["$scope"]
- }
-
- ],
- "views": [
- {
- "name": "Telemetry Table",
- "key": "table",
- "cssClass": "icon-tabular-realtime",
- "template": telemetryTableTemplate,
- "needs": [
- "telemetry"
- ],
- "delegation": true,
- "editable": false
- }
- ],
- "directives": [
- {
- "key": "mctTable",
- "implementation": MCTTable,
- "depends": ["$timeout"]
- }
- ],
- "representations": [
- {
- "key": "table-options-edit",
- "template": tableOptionsEditTemplate
- }
- ]
- }
- });
-
-});
diff --git a/platform/features/table/res/templates/mct-table.html b/platform/features/table/res/templates/mct-table.html
deleted file mode 100644
index 69ab92ef35..0000000000
--- a/platform/features/table/res/templates/mct-table.html
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
- {{header}} |
-
-
- {{sizingRow[header].text}}
- |
-
-
-
diff --git a/platform/features/table/res/templates/table-options-edit.html b/platform/features/table/res/templates/table-options-edit.html
deleted file mode 100644
index df41e0ec3d..0000000000
--- a/platform/features/table/res/templates/table-options-edit.html
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
diff --git a/platform/features/table/res/templates/telemetry-table.html b/platform/features/table/res/templates/telemetry-table.html
deleted file mode 100644
index d76de4c43c..0000000000
--- a/platform/features/table/res/templates/telemetry-table.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
diff --git a/platform/features/table/src/TableColumn.js b/platform/features/table/src/TableColumn.js
deleted file mode 100644
index 971fd0e9ba..0000000000
--- a/platform/features/table/src/TableColumn.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(function () {
- function TableColumn(openmct, telemetryObject, metadatum) {
- this.openmct = openmct;
- this.telemetryObject = telemetryObject;
- this.metadatum = metadatum;
- this.formatter = openmct.telemetry.getValueFormatter(metadatum);
-
- this.titleValue = this.metadatum.name;
- }
-
- TableColumn.prototype.title = function (title) {
- if (arguments.length > 0) {
- this.titleValue = title;
- }
- return this.titleValue;
- };
-
- TableColumn.prototype.isCurrentTimeSystem = function () {
- var isCurrentTimeSystem = this.metadatum.hints.hasOwnProperty('domain') &&
- this.metadatum.key === this.openmct.time.timeSystem().key;
-
- return isCurrentTimeSystem;
- };
-
- TableColumn.prototype.hasValue = function (telemetryObject, telemetryDatum) {
- var keyStringForDatum = this.openmct.objects.makeKeyString(telemetryObject.identifier);
- var keyStringForColumn = this.openmct.objects.makeKeyString(this.telemetryObject.identifier);
- return keyStringForDatum === keyStringForColumn && telemetryDatum.hasOwnProperty(this.metadatum.source);
- };
-
- TableColumn.prototype.getValue = function (telemetryDatum, limitEvaluator) {
- var alarm = limitEvaluator &&
- limitEvaluator.evaluate(telemetryDatum, this.metadatum);
- var value = {
- text: this.formatter.format(telemetryDatum),
- value: this.formatter.parse(telemetryDatum)
- };
-
- if (alarm) {
- value.cssClass = alarm.cssClass;
- }
- return value;
- };
-
- return TableColumn;
-});
diff --git a/platform/features/table/src/TableConfiguration.js b/platform/features/table/src/TableConfiguration.js
deleted file mode 100644
index d5525410aa..0000000000
--- a/platform/features/table/src/TableConfiguration.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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.
- *****************************************************************************/
-/* global Set */
-define(
- ['./TableColumn'],
- function (TableColumn) {
-
- /**
- * Class that manages table metadata, state, and contents.
- * @memberof platform/features/table
- * @param domainObject
- * @constructor
- */
- function TableConfiguration(domainObject, openmct) {
- this.domainObject = domainObject;
- this.openmct = openmct;
- this.timeSystemColumn = undefined;
- this.columns = [];
- this.headers = new Set();
- this.timeSystemColumnTitle = undefined;
- }
-
- /**
- * Build column definition based on supplied telemetry metadata
- * @param telemetryObject the telemetry producing object associated with this column
- * @param metadata Metadata describing the domains and ranges available
- * @returns {TableConfiguration} This object
- */
- TableConfiguration.prototype.addColumn = function (telemetryObject, metadatum) {
- var column = new TableColumn(this.openmct, telemetryObject, metadatum);
-
- if (column.isCurrentTimeSystem()) {
- if (!this.timeSystemColumnTitle) {
- this.timeSystemColumnTitle = column.title();
- }
- column.title(this.timeSystemColumnTitle);
- }
-
- this.columns.push(column);
- this.headers.add(column.title());
- };
-
- /**
- * Retrieve and format values for a given telemetry datum.
- * @param telemetryObject The object that the telemetry data is
- * associated with
- * @param datum The telemetry datum to retrieve values from
- * @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, limitEvaluator, datum) {
- return this.columns.reduce(function (rowObject, column) {
- var columnTitle = column.title();
- var columnValue = {
- text: '',
- value: undefined
- };
- if (rowObject[columnTitle] === undefined) {
- rowObject[columnTitle] = columnValue;
- }
-
- if (column.hasValue(telemetryObject, datum)) {
- columnValue = column.getValue(datum, limitEvaluator);
-
- if (columnValue.text === undefined) {
- columnValue.text = '';
- }
- // Don't replace something with nothing.
- // This occurs when there are multiple columns with the same
- // column title
- if (rowObject[columnTitle].text === undefined ||
- rowObject[columnTitle].text.length === 0) {
- rowObject[columnTitle] = columnValue;
- }
- }
-
- return rowObject;
- }, {});
- };
-
- /**
- * @private
- */
- TableConfiguration.prototype.defaultColumnConfiguration = function () {
- return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
- };
-
- /**
- * Set the established configuration on the domain object
- * @private
- */
- TableConfiguration.prototype.saveColumnConfiguration = function (columnConfig) {
- this.domainObject.useCapability('mutation', function (model) {
- model.configuration = model.configuration || {};
- model.configuration.table = model.configuration.table || {};
- model.configuration.table.columns = columnConfig;
- });
- };
-
- function configChanged(config1, config2) {
- var config1Keys = Object.keys(config1),
- config2Keys = Object.keys(config2);
-
- return (config1Keys.length !== config2Keys.length) ||
- config1Keys.some(function (key) {
- return config1[key] !== config2[key];
- });
- }
-
- /**
- * As part of the process of building the table definition, extract
- * configuration from column definitions.
- * @returns {Object} A configuration object consisting of key-value
- * pairs where the key is the column title, and the value is a
- * boolean indicating whether the column should be shown.
- */
- TableConfiguration.prototype.buildColumnConfiguration = function () {
- var configuration = {},
- //Use existing persisted config, or default it
- defaultConfig = this.defaultColumnConfiguration();
-
- /**
- * For each column header, define a configuration value
- * specifying whether the column is visible or not. Default to
- * existing (persisted) configuration if available
- */
- this.headers.forEach(function (columnTitle) {
- configuration[columnTitle] =
- typeof defaultConfig[columnTitle] === 'undefined' ? true :
- defaultConfig[columnTitle];
- });
-
- //Synchronize column configuration with model
- if (this.domainObject.hasCapability('editor') &&
- this.domainObject.getCapability('editor').isEditContextRoot() &&
- configChanged(configuration, defaultConfig)) {
- this.saveColumnConfiguration(configuration);
- }
-
- return configuration;
- };
-
- return TableConfiguration;
- }
-);
diff --git a/platform/features/table/src/TelemetryCollection.js b/platform/features/table/src/TelemetryCollection.js
deleted file mode 100644
index 69eab0daed..0000000000
--- a/platform/features/table/src/TelemetryCollection.js
+++ /dev/null
@@ -1,249 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- 'lodash',
- 'EventEmitter'
- ],
- function (_, EventEmitter) {
-
- /**
- * @constructor
- */
- function TelemetryCollection() {
- EventEmitter.call(this, arguments);
- this.dupeCheck = false;
- this.telemetry = [];
- this.highBuffer = [];
- this.sortField = undefined;
- this.lastBounds = {};
-
- _.bindAll(this, [
- 'addOne',
- 'iteratee'
- ]);
- }
-
- TelemetryCollection.prototype = Object.create(EventEmitter.prototype);
-
- TelemetryCollection.prototype.iteratee = function (item) {
- return _.get(item, this.sortField);
- };
-
- /**
- * This function is optimized for ticking - it assumes that start and end
- * bounds will only increase and as such this cannot be used for decreasing
- * bounds changes.
- *
- * An implication of this is that data will not be discarded that exceeds
- * the given end bounds. For arbitrary bounds changes, it's assumed that
- * a telemetry requery is performed anyway, and the collection is cleared
- * and repopulated.
- *
- * @fires TelemetryCollection#added
- * @fires TelemetryCollection#discarded
- * @param bounds
- */
- TelemetryCollection.prototype.bounds = function (bounds) {
- var startChanged = this.lastBounds.start !== bounds.start;
- var endChanged = this.lastBounds.end !== bounds.end;
- var startIndex = 0;
- var endIndex = 0;
- var discarded;
- var added;
- var testValue;
-
- this.lastBounds = bounds;
-
- // If collection is not sorted by a time field, we cannot respond to
- // bounds events
- if (this.sortField === undefined) {
- this.lastBounds = bounds;
- return;
- }
-
- if (startChanged) {
- testValue = _.set({}, this.sortField, bounds.start);
- // Calculate the new index of the first item within the bounds
- startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField);
- discarded = this.telemetry.splice(0, startIndex);
- }
- if (endChanged) {
- testValue = _.set({}, this.sortField, bounds.end);
- // Calculate the new index of the last item in bounds
- endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
- added = this.highBuffer.splice(0, endIndex);
- added.forEach(function (datum) {
- this.telemetry.push(datum);
- }.bind(this));
- }
-
- if (discarded && discarded.length > 0) {
- /**
- * 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
- */
- this.emit('discarded', discarded);
- }
- if (added && added.length > 0) {
- /**
- * 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
- */
- this.emit('added', added);
- }
- };
-
- /**
- * Adds an individual item to the collection. Used internally only
- * @private
- * @param item
- */
- TelemetryCollection.prototype.addOne = function (item) {
- var isDuplicate = false;
- var boundsDefined = this.lastBounds &&
- (this.lastBounds.start !== undefined && this.lastBounds.end !== undefined);
- var array;
- var boundsLow;
- var boundsHigh;
-
- // If collection is not sorted by a time field, we cannot respond to
- // bounds events, so no bounds checking necessary
- if (this.sortField === undefined) {
- this.telemetry.push(item);
-
- return true;
- }
-
- // Insert into either in-bounds array, or the out of bounds high buffer.
- // Data in the high buffer will be re-evaluated for possible insertion on next tick
-
- if (boundsDefined) {
- boundsHigh = _.get(item, this.sortField) > this.lastBounds.end;
- boundsLow = _.get(item, this.sortField) < this.lastBounds.start;
-
- if (!boundsHigh && !boundsLow) {
- array = this.telemetry;
- } else if (boundsHigh) {
- array = this.highBuffer;
- }
- } else {
- array = this.telemetry;
- }
-
- // If out of bounds low, disregard data
- if (!boundsLow) {
- // Going to check for duplicates. Bound the search problem to
- // items around the given time. Use sortedIndex because it
- // employs a binary search which is O(log n). Can use binary search
- // based on time stamp because the array is guaranteed ordered due
- // to sorted insertion.
- var startIx = _.sortedIndex(array, item, this.sortField);
- var endIx;
-
- if (this.dupeCheck && startIx !== array.length) {
- endIx = _.sortedLastIndex(array, item, this.sortField);
-
- // Create an array of potential dupes, based on having the
- // same time stamp
- var potentialDupes = array.slice(startIx, endIx + 1);
- // Search potential dupes for exact dupe
- isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1;
- }
-
- if (!isDuplicate) {
- array.splice(endIx || startIx, 0, item);
-
- //Return true if it was added and in bounds
- return array === this.telemetry;
- }
- }
- return false;
- };
-
- /**
- * Add an array of objects to this telemetry collection
- * @fires TelemetryCollection#added
- * @param {object[]} items
- */
- TelemetryCollection.prototype.add = function (items) {
- var added = items.filter(this.addOne);
- this.emit('added', added);
- this.dupeCheck = true;
- };
-
- /**
- * Clears the contents of the telemetry collection
- */
- TelemetryCollection.prototype.clear = function () {
- this.telemetry = [];
- this.highBuffer = [];
- };
-
- /**
- * Sorts the telemetry collection based on the provided sort field
- * 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();
- * let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
- * return {
- * // define an object property to demonstrate nested paths
- * timestamp: {
- * ms: now - value * 1000,
- * text:
- * },
- * value: value
- * }
- * });
- * let collection = new TelemetryCollection();
- *
- * collection.add(telemetry);
- *
- * // Sort by telemetry value
- * collection.sort("value");
- *
- * // Sort by ms since epoch
- * collection.sort("timestamp.ms");
- *
- * // Sort by formatted date text
- * collection.sort("timestamp.text");
- *
- *
- * @param {string} sortField An object property path.
- */
- TelemetryCollection.prototype.sort = function (sortField) {
- this.sortField = sortField;
- if (sortField !== undefined) {
- this.telemetry = _.sortBy(this.telemetry, this.iteratee);
- }
- };
-
- return TelemetryCollection;
- }
-);
diff --git a/platform/features/table/src/controllers/MCTTableController.js b/platform/features/table/src/controllers/MCTTableController.js
deleted file mode 100644
index 7c8242b03e..0000000000
--- a/platform/features/table/src/controllers/MCTTableController.js
+++ /dev/null
@@ -1,828 +0,0 @@
-
-define(
- [
- 'zepto',
- 'lodash'
- ],
- function ($, _) {
-
- /**
- * A controller for the MCTTable directive. Populates scope with
- * data used for populating, sorting, and filtering
- * tables.
- * @param $scope
- * @param $timeout
- * @param element
- * @constructor
- */
- function MCTTableController($scope, $window, element, exportService, formatService, openmct) {
- var self = this;
-
- this.$scope = $scope;
- this.element = $(element[0]);
- this.$window = $window;
- this.maxDisplayRows = 100;
-
- this.scrollable = this.element.find('.t-scrolling').first();
- this.resultsHeader = this.element.find('.mct-table>thead').first();
- this.sizingTableBody = this.element.find('.t-sizing-table>tbody').first();
- this.$scope.sizingRow = {};
- this.$scope.calcTableWidthPx = '100%';
- this.timeApi = openmct.time;
- this.toiFormatter = undefined;
- this.formatService = formatService;
- this.callbacks = {};
-
- //Bind all class functions to 'this'
- _.bindAll(this, [
- 'addRows',
- 'binarySearch',
- 'buildLargestRow',
- 'changeBounds',
- 'changeTimeOfInterest',
- 'changeTimeSystem',
- 'destroyConductorListeners',
- 'digest',
- 'filterAndSort',
- 'filterRows',
- 'firstVisible',
- 'insertSorted',
- 'lastVisible',
- 'onRowClick',
- 'onScroll',
- 'removeRows',
- 'resize',
- 'scrollToBottom',
- 'scrollToRow',
- 'setElementSizes',
- 'setHeaders',
- 'setRows',
- 'setTimeOfInterestRow',
- 'setVisibleRows',
- 'sortComparator',
- 'sortRows'
- ]);
-
- this.scrollable.on('scroll', this.onScroll);
-
- $scope.visibleRows = [];
- $scope.displayRows = [];
-
- /**
- * Set default values for optional parameters on a given scope
- */
- function setDefaults(scope) {
- if (typeof scope.enableFilter === 'undefined') {
- scope.enableFilter = true;
- scope.filters = {};
- }
- if (typeof scope.enableSort === 'undefined') {
- scope.enableSort = true;
- scope.sortColumn = undefined;
- scope.sortDirection = undefined;
- }
- if (scope.sortColumn !== undefined) {
- scope.sortDirection = "asc";
- }
- }
-
- setDefaults($scope);
-
- $scope.exportAsCSV = function () {
- var headers = $scope.displayHeaders,
- filename = $(element[0]).attr('export-as');
-
- exportService.exportCSV($scope.displayRows.map(function (row) {
- return headers.reduce(function (r, header) {
- r[header] = row[header].text;
- return r;
- }, {});
- }), {
- headers: headers,
- filename: filename
- });
- };
-
- $scope.toggleSort = function (key) {
- if (!$scope.enableSort) {
- return;
- }
- if ($scope.sortColumn !== key) {
- $scope.sortColumn = key;
- $scope.sortDirection = 'asc';
- } else if ($scope.sortDirection === 'asc') {
- $scope.sortDirection = 'desc';
- } else if ($scope.sortDirection === 'desc') {
- $scope.sortColumn = undefined;
- $scope.sortDirection = undefined;
- } else if ($scope.sortColumn !== undefined &&
- $scope.sortDirection === undefined) {
- $scope.sortDirection = 'asc';
- }
- self.setRows($scope.rows);
- self.setTimeOfInterestRow(self.timeApi.timeOfInterest());
- };
-
- /*
- * Define watches to listen for changes to headers and rows.
- */
- $scope.$watchCollection('filters', function () {
- self.setRows($scope.rows);
- });
- $scope.$watch('headers', function (newHeaders, oldHeaders) {
- if (newHeaders !== oldHeaders) {
- this.setHeaders(newHeaders);
- }
- }.bind(this));
- $scope.$watch('rows', this.setRows);
-
- /*
- * Listen for rows added individually (eg. for real-time tables)
- */
- $scope.$on('add:rows', this.addRows);
- $scope.$on('remove:rows', this.removeRows);
-
- /**
- * Populated from the default-sort attribute on MctTable
- * directive tag.
- */
- $scope.$watch('defaultSort', function (newColumn, oldColumn) {
- if (newColumn !== oldColumn) {
- $scope.toggleSort(newColumn);
- }
- });
-
- /*
- * Listen for resize events to trigger recalculation of table width
- */
- $scope.resize = this.setElementSizes;
-
- /**
- * Scope variable that is populated from the 'time-columns'
- * attribute on the MctTable tag. Indicates which columns, while
- * sorted, can be used for indicated time of interest.
- */
- $scope.$watch("timeColumns", function (timeColumns) {
- if (timeColumns) {
- this.destroyConductorListeners();
-
- this.timeApi.on('timeSystem', this.changeTimeSystem);
- this.timeApi.on('timeOfInterest', this.changeTimeOfInterest);
- this.timeApi.on('bounds', this.changeBounds);
-
- // If time system defined, set initially
- if (this.timeApi.timeSystem() !== undefined) {
- this.changeTimeSystem(this.timeApi.timeSystem());
- }
- }
- }.bind(this));
-
- $scope.$on('$destroy', function () {
- this.scrollable.off('scroll', this.onScroll);
- this.destroyConductorListeners();
-
- }.bind(this));
- }
-
- MCTTableController.prototype.destroyConductorListeners = function () {
- this.timeApi.off('timeSystem', this.changeTimeSystem);
- this.timeApi.off('timeOfInterest', this.changeTimeOfInterest);
- this.timeApi.off('bounds', this.changeBounds);
- };
-
- MCTTableController.prototype.changeTimeSystem = function (timeSystem) {
- var format = timeSystem.timeFormat;
- this.toiFormatter = this.formatService.getFormat(format);
- };
-
- /**
- * If auto-scroll is enabled, this function will scroll to the
- * bottom of the page
- * @private
- */
- MCTTableController.prototype.scrollToBottom = function () {
- this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
- };
-
- /**
- * Handles a row add event. Rows can be added as needed using the
- * `add:row` broadcast event.
- * @private
- */
- MCTTableController.prototype.addRows = function (event, rows) {
- //Does the row pass the current filter?
- if (this.filterRows(rows).length > 0) {
- rows.forEach(this.insertSorted.bind(this, this.$scope.displayRows));
-
- //Resize the columns , then update the rows visible in the table
- this.resize([this.$scope.sizingRow].concat(rows))
- .then(this.setVisibleRows)
- .then(function () {
- if (this.$scope.autoScroll) {
- this.scrollToBottom();
- }
- }.bind(this));
-
- var toi = this.timeApi.timeOfInterest();
- if (toi !== -1) {
- this.setTimeOfInterestRow(toi);
- }
- }
- };
-
- /**
- * Handles a row remove event. Rows can be removed as needed using the
- * `remove:row` broadcast event.
- * @private
- */
- MCTTableController.prototype.removeRows = function (event, rows) {
- var indexInDisplayRows;
- rows.forEach(function (row) {
- // Do a sequential search here. Only way of finding row is by
- // object equality, so array is in effect unsorted.
- indexInDisplayRows = this.$scope.displayRows.indexOf(row);
- if (indexInDisplayRows !== -1) {
- this.$scope.displayRows.splice(indexInDisplayRows, 1);
- }
- }, this);
-
- this.$scope.sizingRow = this.buildLargestRow([this.$scope.sizingRow].concat(rows));
-
- this.setElementSizes();
- this.setVisibleRows()
- .then(function () {
- if (this.$scope.autoScroll) {
- this.scrollToBottom();
- }
- }.bind(this));
-
- };
-
- /**
- * @private
- */
- MCTTableController.prototype.onScroll = function (event) {
- this.scrollWindow = {
- top: this.scrollable[0].scrollTop,
- bottom: this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight,
- offsetHeight: this.scrollable[0].offsetHeight,
- height: this.scrollable[0].scrollHeight
- };
- this.$window.requestAnimationFrame(function () {
- this.setVisibleRows();
- this.digest();
-
- // If user scrolls away from bottom, disable auto-scroll.
- // Auto-scroll will be re-enabled if user scrolls to bottom again.
- if (this.scrollWindow.top <
- (this.scrollWindow.height - this.scrollWindow.offsetHeight) - 20) {
- this.$scope.autoScroll = false;
- } else {
- this.$scope.autoScroll = true;
- }
- this.scrolling = false;
- delete this.scrollWindow;
- }.bind(this));
- };
-
- /**
- * Return first visible row, based on current scroll state.
- * @private
- */
- MCTTableController.prototype.firstVisible = function () {
- var topScroll = this.scrollWindow ?
- this.scrollWindow.top :
- this.scrollable[0].scrollTop;
-
- return Math.floor(
- (topScroll) / this.$scope.rowHeight
- );
-
- };
-
- /**
- * Return last visible row, based on current scroll state.
- * @private
- */
- MCTTableController.prototype.lastVisible = function () {
- var bottomScroll = this.scrollWindow ?
- this.scrollWindow.bottom :
- this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight;
-
- return Math.ceil(
- (bottomScroll) /
- this.$scope.rowHeight
- );
- };
-
- /**
- * Sets visible rows based on array
- * content and current scroll state.
- */
- MCTTableController.prototype.setVisibleRows = function () {
- var self = this,
- totalVisible,
- numberOffscreen,
- firstVisible,
- lastVisible,
- start,
- end;
-
- //No need to scroll
- if (this.$scope.displayRows.length < this.maxDisplayRows) {
- start = 0;
- end = this.$scope.displayRows.length;
- } else {
- firstVisible = this.firstVisible();
- lastVisible = this.lastVisible();
- totalVisible = lastVisible - firstVisible;
- numberOffscreen = this.maxDisplayRows - totalVisible;
- start = firstVisible - Math.floor(numberOffscreen / 2);
- end = lastVisible + Math.ceil(numberOffscreen / 2);
-
- if (start < 0) {
- start = 0;
- end = Math.min(this.maxDisplayRows,
- this.$scope.displayRows.length);
- } else if (end >= this.$scope.displayRows.length) {
- end = this.$scope.displayRows.length;
- start = end - this.maxDisplayRows + 1;
- }
- if (this.$scope.visibleRows[0] &&
- this.$scope.visibleRows[0].rowIndex === start &&
- this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
- .rowIndex === end) {
- return this.digest();
- }
- }
- //Set visible rows from display rows, based on calculated offset.
- this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
- .map(function (row, i) {
- return {
- rowIndex: start + i,
- offsetY: ((start + i) * self.$scope.rowHeight),
- contents: row
- };
- });
- return this.digest();
- };
-
- /**
- * Update table headers with new headers. If filtering is
- * enabled, reset filters. If sorting is enabled, reset
- * sorting.
- */
- MCTTableController.prototype.setHeaders = function (newHeaders) {
- if (!newHeaders) {
- return;
- }
-
- this.$scope.displayHeaders = newHeaders;
- if (this.$scope.enableFilter) {
- this.$scope.filters = {};
- }
- // Reset column sort information unless the new headers
- // contain the column currently sorted on.
- if (this.$scope.enableSort &&
- newHeaders.indexOf(this.$scope.sortColumn) === -1) {
- this.$scope.sortColumn = undefined;
- this.$scope.sortDirection = undefined;
- }
- this.setRows(this.$scope.rows);
- };
-
- /**
- * Read styles from the DOM and use them to calculate offsets
- * for individual rows.
- */
- MCTTableController.prototype.setElementSizes = function () {
- var tbody = this.sizingTableBody,
- firstRow = tbody.find('tr'),
- column = firstRow.find('td'),
- rowHeight = firstRow.prop('offsetHeight'),
- columnWidth,
- tableWidth = 0,
- overallHeight = (rowHeight *
- (this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
-
- this.$scope.columnWidths = [];
-
- while (column.length) {
- columnWidth = column.prop('offsetWidth');
- this.$scope.columnWidths.push(column.prop('offsetWidth'));
- tableWidth += columnWidth;
- column = column.next();
- }
- this.$scope.rowHeight = rowHeight;
- this.$scope.totalHeight = overallHeight;
-
- var scrollW = this.scrollable[0].offsetWidth - this.scrollable[0].clientWidth;
- if (scrollW && scrollW > 0) {
- this.$scope.calcTableWidthPx = 'calc(100% - ' + scrollW + 'px)';
- }
-
- if (tableWidth > 0) {
- this.$scope.totalWidth = tableWidth + 'px';
- } else {
- this.$scope.totalWidth = 'none';
- }
- };
-
- /**
- * Finds the correct insertion point for a new row, which takes into
- * account duplicates to make sure new rows are inserted in a way that
- * maintains arrival order.
- *
- * @private
- * @param {Array} searchArray
- * @param {Object} searchElement Object to find the insertion point for
- */
- MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
- var index;
- var testIndex;
- var first = searchArray[0];
- var last = searchArray[searchArray.length - 1];
-
- if (first) {
- first = first[this.$scope.sortColumn].text;
- }
- if (last) {
- last = last[this.$scope.sortColumn].text;
- }
- // Shortcut check for append/prepend
- if (first && this.sortComparator(first, searchElement) >= 0) {
- index = testIndex = 0;
- } else if (last && this.sortComparator(last, searchElement) <= 0) {
- index = testIndex = searchArray.length;
- } else {
- // use a binary search to find the correct insertion point
- index = testIndex = this.binarySearch(
- searchArray,
- searchElement,
- 0,
- searchArray.length - 1
- );
- }
-
- //It's possible that the insertion point is a duplicate of the element to be inserted
- var isDupe = function () {
- return this.sortComparator(searchElement,
- searchArray[testIndex][this.$scope.sortColumn].text) === 0;
- }.bind(this);
-
- // In the event of a duplicate, scan left or right (depending on
- // sort order) to find an insertion point that maintains order received
- while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) {
- if (this.$scope.sortDirection === 'asc') {
- index = ++testIndex;
- } else {
- index = testIndex--;
- }
- }
- return index;
- };
-
- /**
- * @private
- */
- MCTTableController.prototype.binarySearch = function (searchArray, searchElement, min, max) {
- var sampleAt = Math.floor((max - min) / 2) + min;
-
- if (max < min) {
- return min; // Element is not in array, min gives direction
- }
- switch (this.sortComparator(searchElement,
- searchArray[sampleAt][this.$scope.sortColumn].text)) {
- case -1:
- return this.binarySearch(searchArray, searchElement, min,
- sampleAt - 1);
- case 0:
- return sampleAt;
- case 1:
- return this.binarySearch(searchArray, searchElement,
- sampleAt + 1, max);
- }
- };
-
- /**
- * @private
- */
- MCTTableController.prototype.insertSorted = function (array, element) {
- var index = -1;
-
- if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
- //No sorting applied, push it on the end.
- index = array.length;
- } else {
- //Sort is enabled, perform binary search to find insertion point
- index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text);
- }
- if (index === -1) {
- array.unshift(element);
- } else if (index === array.length) {
- array.push(element);
- } else {
- array.splice(index, 0, element);
- }
- };
-
- /**
- * Compare two variables, returning a number that represents
- * which is larger. Similar to the default array sort
- * comparator, but does not coerce all values to string before
- * conversion. Strings are lowercased before comparison.
- *
- * @private
- */
- MCTTableController.prototype.sortComparator = function (a, b) {
- var result = 0,
- sortDirectionMultiplier,
- numberA,
- numberB;
- /**
- * Given a value, if it is a number, or a string representation of a
- * number, then return a number representation. Otherwise, return
- * the original value. It's a little more robust than using just
- * Number() or parseFloat, or isNaN in isolation, all of which are
- * fairly inconsistent in their results.
- * @param value The value to return as a number.
- * @returns {*} The value cast to a Number, or the original value if
- * a Number representation is not possible.
- */
- function toNumber(value) {
- var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
- return val;
- }
-
- numberA = toNumber(a);
- numberB = toNumber(b);
-
- //If they're both numbers, then compare them as numbers
- if (typeof numberA === "number" && typeof numberB === "number") {
- a = numberA;
- b = numberB;
- }
-
- //If they're both strings, then ignore case
- if (typeof a === "string" && typeof b === "string") {
- a = a.toLowerCase();
- b = b.toLowerCase();
- }
-
- if (a < b) {
- result = -1;
- }
- if (a > b) {
- result = 1;
- }
-
- if (this.$scope.sortDirection === 'asc') {
- sortDirectionMultiplier = 1;
- } else if (this.$scope.sortDirection === 'desc') {
- sortDirectionMultiplier = -1;
- }
-
- return result * sortDirectionMultiplier;
- };
-
- /**
- * Returns a new array which is a result of applying the sort
- * criteria defined in $scope.
- *
- * Does not modify the array that was passed in.
- */
- MCTTableController.prototype.sortRows = function (rowsToSort) {
- var self = this,
- sortKey = this.$scope.sortColumn;
-
- if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
- return rowsToSort;
- }
-
- return rowsToSort.sort(function (a, b) {
- return self.sortComparator(a[sortKey].text, b[sortKey].text);
- });
- };
-
- /**
- * Returns an object which contains the largest values
- * for each key in the given set of rows. This is used to
- * pre-calculate optimal column sizes without having to render
- * every row.
- */
- MCTTableController.prototype.buildLargestRow = function (rows) {
- var largestRow = rows.reduce(function (prevLargest, row) {
- Object.keys(row).forEach(function (key) {
- var currentColumn,
- currentColumnLength,
- largestColumn,
- largestColumnLength;
- if (row[key]) {
- currentColumn = (row[key]).text;
- currentColumnLength =
- (currentColumn && currentColumn.length) ?
- currentColumn.length :
- currentColumn;
- largestColumn = prevLargest[key] ? prevLargest[key].text : "";
- largestColumnLength = largestColumn.length;
-
- if (currentColumnLength > largestColumnLength) {
- prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
- }
- }
- });
- return prevLargest;
- }, JSON.parse(JSON.stringify(rows[0] || {})));
- return largestRow;
- };
-
- // Will effectively cap digests at 60Hz
- // Also turns digest into a promise allowing code to force digest, then
- // schedule something to happen afterwards
- MCTTableController.prototype.digest = function () {
- var scope = this.$scope;
- var self = this;
- var raf = this.$window.requestAnimationFrame;
- var promise = this.digestPromise;
-
- if (!promise) {
- self.digestPromise = promise = new Promise(function (resolve) {
- raf(function () {
- scope.$digest();
- self.digestPromise = undefined;
- resolve();
- });
- });
- }
-
- return promise;
- };
-
- /**
- * 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 (rows) {
- this.$scope.sizingRow = this.buildLargestRow(rows);
- return this.digest().then(this.setElementSizes);
- };
-
- /**
- * @private
- */
- MCTTableController.prototype.filterAndSort = function (rows) {
- var displayRows = rows;
- if (this.$scope.enableFilter) {
- displayRows = this.filterRows(displayRows);
- }
-
- if (this.$scope.enableSort) {
- displayRows = this.sortRows(displayRows.slice(0));
- }
- return displayRows;
- };
-
- /**
- * Update rows with new data. If filtering is enabled, rows
- * will be sorted before display.
- */
- MCTTableController.prototype.setRows = function (newRows) {
- //Nothing to show because no columns visible
- if (!this.$scope.displayHeaders || !newRows) {
- return;
- }
-
- this.$scope.displayRows = this.filterAndSort(newRows || []);
- return this.resize(newRows)
- .then(function (rows) {
- return this.setVisibleRows(rows);
- }.bind(this))
- //Timeout following setVisibleRows to allow digest to
- // perform DOM changes, otherwise scrollTo won't work.
- .then(function () {
- //If TOI specified, scroll to it
- var timeOfInterest = this.timeApi.timeOfInterest();
- if (timeOfInterest) {
- this.setTimeOfInterestRow(timeOfInterest);
- this.scrollToRow(this.$scope.toiRowIndex);
- }
- }.bind(this));
- };
-
- /**
- * Applies user defined filters to rows. These filters are based on
- * the text entered in the search areas in each column.
- * @param rowsToFilter {Object[]} The rows to apply filters to
- * @returns {Object[]} A filtered copy of the supplied rows
- */
- MCTTableController.prototype.filterRows = function (rowsToFilter) {
- var filters = {},
- self = this;
-
- /**
- * Returns true if row matches all filters.
- */
- function matchRow(filterMap, row) {
- return Object.keys(filterMap).every(function (key) {
- if (!row[key]) {
- return false;
- }
- var testVal = String(row[key].text).toLowerCase();
- return testVal.indexOf(filterMap[key]) !== -1;
- });
- }
-
- if (!Object.keys(this.$scope.filters).length) {
- return rowsToFilter;
- }
-
- Object.keys(this.$scope.filters).forEach(function (key) {
- if (!self.$scope.filters[key]) {
- return;
- }
- filters[key] = self.$scope.filters[key].toLowerCase();
- });
-
- return rowsToFilter.filter(matchRow.bind(null, filters));
- };
-
- /**
- * Scroll the view to a given row index
- * @param displayRowIndex {number} The index in the displayed rows
- * to scroll to.
- */
- MCTTableController.prototype.scrollToRow = function (displayRowIndex) {
-
- var visible = displayRowIndex > this.firstVisible() && displayRowIndex < this.lastVisible();
-
- if (!visible) {
- var scrollTop = displayRowIndex * this.$scope.rowHeight +
- (this.scrollable[0].offsetHeight / 2);
- this.scrollable[0].scrollTop = scrollTop;
- this.setVisibleRows();
- }
- };
-
- /**
- * Update rows with new data. If filtering is enabled, rows
- * will be sorted before display.
- */
- MCTTableController.prototype.setTimeOfInterestRow = function (newTOI) {
- var isSortedByTime =
- this.$scope.timeColumns &&
- this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
-
- this.$scope.toiRowIndex = -1;
-
- if (newTOI && isSortedByTime) {
- var formattedTOI = this.toiFormatter.format(newTOI);
- var rowIndex = this.binarySearch(
- this.$scope.displayRows,
- formattedTOI,
- 0,
- this.$scope.displayRows.length - 1);
-
- if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
- this.$scope.toiRowIndex = rowIndex;
- }
- }
- };
-
- MCTTableController.prototype.changeTimeOfInterest = function (newTOI) {
- this.setTimeOfInterestRow(newTOI);
- this.scrollToRow(this.$scope.toiRowIndex);
- };
-
- /**
- * On zoom, pan, etc. reset TOI
- * @param bounds
- */
- MCTTableController.prototype.changeBounds = function (bounds) {
- this.setTimeOfInterestRow(this.timeApi.timeOfInterest());
- if (this.$scope.toiRowIndex !== -1) {
- this.scrollToRow(this.$scope.toiRowIndex);
- }
- };
-
- /**
- * @private
- */
- MCTTableController.prototype.onRowClick = function (event, rowIndex) {
- if (this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1) {
- var selectedTime = this.$scope.displayRows[rowIndex][this.$scope.sortColumn].text;
- if (selectedTime &&
- this.toiFormatter.validate(selectedTime) &&
- event.altKey) {
- this.timeApi.timeOfInterest(this.toiFormatter.parse(selectedTime));
- }
- }
- };
-
- return MCTTableController;
- }
-);
diff --git a/platform/features/table/src/controllers/TableOptionsController.js b/platform/features/table/src/controllers/TableOptionsController.js
deleted file mode 100644
index a9d37c924d..0000000000
--- a/platform/features/table/src/controllers/TableOptionsController.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [],
- function () {
-
- /**
- * Notes on implementation of plot options
- *
- * Multiple y-axes will have to be handled with multiple forms as
- * they will need to be stored on distinct model object
- *
- * Likewise plot series options per-child will need to be separate
- * forms.
- */
-
- /**
- * The LayoutController is responsible for supporting the
- * Layout view. It arranges frames according to saved configuration
- * and provides methods for updating these based on mouse
- * movement.
- * @memberof platform/features/plot
- * @constructor
- * @param {Scope} $scope the controller's Angular scope
- */
- function TableOptionsController($scope) {
-
- var self = this;
-
- this.$scope = $scope;
- this.domainObject = $scope.domainObject;
- this.listeners = [];
-
- $scope.columnsForm = {};
-
- function unlisten() {
- self.listeners.forEach(function (listener) {
- listener();
- });
- }
-
- $scope.$watch('domainObject', function (domainObject) {
- unlisten();
- 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 (newColumns, oldColumns) {
- if (newColumns !== oldColumns) {
- self.domainObject.useCapability('mutation', function (model) {
- model.configuration.table.columns = newColumns;
- });
- self.domainObject.getCapability('persistence').persist();
- }
- });
-
- /**
- * Destroy all mutation listeners
- */
- $scope.$on('$destroy', unlisten);
-
- }
-
- TableOptionsController.prototype.populateForm = function (model) {
- var columnsDefinition = (((model.configuration || {}).table || {}).columns || {}),
- rows = [];
- this.$scope.columnsForm = {
- 'name': 'Columns',
- 'sections': [{
- 'name': 'Columns',
- 'rows': rows
- }]};
-
- Object.keys(columnsDefinition).forEach(function (key) {
- rows.push({
- 'name': key,
- 'control': 'checkbox',
- 'key': key
- });
- });
- 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
deleted file mode 100644
index 1789b27554..0000000000
--- a/platform/features/table/src/controllers/TelemetryTableController.js
+++ /dev/null
@@ -1,450 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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.
- *****************************************************************************/
-/* global console*/
-
-/**
- * This bundle adds a table view for displaying telemetry data.
- * @namespace platform/features/table
- */
-define(
- [
- '../TableConfiguration',
- '../../../../../src/api/objects/object-utils',
- '../TelemetryCollection',
- 'lodash'
-
- ],
- function (TableConfiguration, objectUtils, TelemetryCollection, _) {
-
- /**
- * The TableController is responsible for getting data onto the page
- * in the table widget. This includes handling composition,
- * configuration, and telemetry subscriptions.
- * @memberof platform/features/table
- * @param $scope
- * @constructor
- */
- function TelemetryTableController(
- $scope,
- $timeout,
- openmct
- ) {
-
- this.$scope = $scope;
- this.$timeout = $timeout;
- this.openmct = openmct;
- this.batchSize = 1000;
-
- /*
- * Initialization block
- */
- this.columns = {}; //Range and Domain columns
- this.unobserveObject = undefined;
- this.subscriptions = [];
- this.timeColumns = [];
- $scope.rows = [];
- this.table = new TableConfiguration($scope.domainObject,
- openmct);
- this.lastBounds = this.openmct.time.bounds();
- this.lastRequestTime = 0;
- this.telemetry = new TelemetryCollection();
- if (this.lastBounds) {
- this.telemetry.bounds(this.lastBounds);
- }
-
- /*
- * Create a new format object from legacy object, and replace it
- * when it changes
- */
- this.domainObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
- $scope.domainObject.getId());
-
- this.$scope.exportAs = this.$scope.domainObject.getModel().name;
-
- _.bindAll(this, [
- 'destroy',
- 'sortByTimeSystem',
- 'loadColumns',
- 'getHistoricalData',
- 'subscribeToNewData',
- 'changeBounds',
- 'setClock',
- 'addRowsToTable',
- 'removeRowsFromTable'
- ]);
-
- // Retrieve data when domain object is available.
- // Also deferring telemetry request makes testing easier as controller
- // construction has no unintended consequences.
- $scope.$watch("domainObject", function () {
- this.getData();
- this.registerChangeListeners();
- }.bind(this));
-
- this.setClock(this.openmct.time.clock());
-
- this.$scope.$on("$destroy", this.destroy);
- }
-
- /**
- * @private
- * @param {boolean} scroll
- */
- TelemetryTableController.prototype.setClock = function (clock) {
- this.$scope.autoScroll = clock !== undefined;
- };
-
- /**
- * Based on the selected time system, find a matching domain column
- * to sort by. By default will just match on key.
- *
- * @private
- */
- TelemetryTableController.prototype.sortByTimeSystem = function () {
- var scope = this.$scope;
- var sortColumn;
- scope.defaultSort = undefined;
-
- sortColumn = this.table.columns.filter(function (column) {
- return column.isCurrentTimeSystem();
- })[0];
- if (sortColumn) {
- scope.defaultSort = sortColumn.title();
- this.telemetry.sort(sortColumn.title() + '.value');
- }
- };
-
- /**
- * Attaches listeners that respond to state change in domain object,
- * conductor, and receipt of telemetry
- *
- * @private
- */
- TelemetryTableController.prototype.registerChangeListeners = function () {
- if (this.unobserveObject) {
- this.unobserveObject();
- }
-
- this.unobserveObject = this.openmct.objects.observe(this.domainObject, "*",
- function (domainObject) {
- this.domainObject = domainObject;
- this.getData();
- }.bind(this)
- );
-
- this.openmct.time.on('timeSystem', this.sortByTimeSystem);
- this.openmct.time.on('bounds', this.changeBounds);
- this.openmct.time.on('clock', this.setClock);
-
- this.telemetry.on('added', this.addRowsToTable);
- this.telemetry.on('discarded', this.removeRowsFromTable);
- };
-
- /**
- * On receipt of new telemetry, informs mct-table directive that new rows
- * are available and passes populated rows to it
- *
- * @private
- * @param rows
- */
- TelemetryTableController.prototype.addRowsToTable = function (rows) {
- this.$scope.$broadcast('add:rows', rows);
- };
-
- /**
- * When rows are to be removed, informs mct-table directive. Row removal
- * happens when rows call outside the bounds of the time conductor
- *
- * @private
- * @param rows
- */
- TelemetryTableController.prototype.removeRowsFromTable = function (rows) {
- this.$scope.$broadcast('remove:rows', rows);
- };
-
- /**
- * On Time Conductor bounds change, update displayed telemetry. In the
- * case of a tick, previously visible telemetry that is now out of band
- * will be removed from the table.
- * @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
- */
- TelemetryTableController.prototype.changeBounds = function (bounds, isTick) {
- if (isTick) {
- this.telemetry.bounds(bounds);
- } else {
- // Is fixed bounds change
- this.getData();
- }
- this.lastBounds = bounds;
- };
-
- /**
- * Clean controller, deregistering listeners etc.
- */
- TelemetryTableController.prototype.destroy = function () {
-
- this.openmct.time.off('timeSystem', this.sortByTimeSystem);
- this.openmct.time.off('bounds', this.changeBounds);
- this.openmct.time.off('clock', this.setClock);
-
- this.subscriptions.forEach(function (subscription) {
- subscription();
- });
-
- if (this.unobserveObject) {
- this.unobserveObject();
- }
- this.subscriptions = [];
-
- if (this.timeoutHandle) {
- this.$timeout.cancel(this.timeoutHandle);
- }
- };
-
- /**
- * For given objects, populate column metadata and table headers.
- * @private
- * @param {module:openmct.DomainObject[]} objects the domain objects for
- * which columns should be populated
- */
- TelemetryTableController.prototype.loadColumns = function (objects) {
- var telemetryApi = this.openmct.telemetry;
-
- this.table = new TableConfiguration(this.$scope.domainObject,
- this.openmct);
-
- this.$scope.headers = [];
-
- if (objects.length > 0) {
- objects.forEach(function (object) {
- var metadataValues = telemetryApi.getMetadata(object).values();
- metadataValues.forEach(function (metadatum) {
- this.table.addColumn(object, metadatum);
- }.bind(this));
- }.bind(this));
-
- this.filterColumns();
- this.sortByTimeSystem();
- }
-
- return objects;
- };
-
- /**
- * Request telemetry data from an historical store for given objects.
- * @private
- * @param {object[]} The domain objects to request telemetry for
- * @returns {Promise} resolved when historical data is available
- */
- TelemetryTableController.prototype.getHistoricalData = function (objects) {
- var self = this;
- var openmct = this.openmct;
- var bounds = openmct.time.bounds();
- var scope = this.$scope;
- var rowData = [];
- var processedObjects = 0;
- var requestTime = this.lastRequestTime = Date.now();
- var telemetryCollection = this.telemetry;
-
- var promise = new Promise(function (resolve, reject) {
- /*
- * On completion of batched processing, set the rows on scope
- */
- function finishProcessing() {
- telemetryCollection.add(rowData);
- scope.rows = telemetryCollection.telemetry;
- self.loading(false);
-
- resolve(scope.rows);
- }
-
- /*
- * Process a batch of historical data
- */
- function processData(object, historicalData, index, limitEvaluator) {
- if (index >= historicalData.length) {
- processedObjects++;
-
- if (processedObjects === objects.length) {
- finishProcessing();
- }
- } else {
- rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
- .map(self.table.getRowValues.bind(self.table, object, limitEvaluator)));
- /*
- Use timeout to yield process to other UI activities. On
- return, process next batch
- */
- self.timeoutHandle = self.$timeout(function () {
- processData(object, historicalData, index + self.batchSize, limitEvaluator);
- });
- }
- }
-
- function makeTableRows(object, historicalData) {
- // Only process the most recent request
- if (requestTime === self.lastRequestTime) {
- var limitEvaluator = openmct.telemetry.limitEvaluator(object);
- processData(object, historicalData, 0, limitEvaluator);
- } else {
- resolve(rowData);
- }
- }
-
- /*
- Use the telemetry API to request telemetry for a given object
- */
- function requestData(object) {
- return openmct.telemetry.request(object, {
- start: bounds.start,
- end: bounds.end
- }).then(makeTableRows.bind(undefined, object))
- .catch(reject);
- }
- this.$timeout.cancel(this.timeoutHandle);
-
- if (objects.length > 0) {
- objects.forEach(requestData);
- } else {
- self.loading(false);
- resolve([]);
- }
- }.bind(this));
-
- return promise;
- };
-
-
- /**
- * Subscribe to real-time data for the given objects.
- * @private
- * @param {object[]} objects The objects to subscribe to.
- */
- TelemetryTableController.prototype.subscribeToNewData = function (objects) {
- var telemetryApi = this.openmct.telemetry;
- var telemetryCollection = this.telemetry;
- //Set table max length to avoid unbounded growth.
- var limitEvaluator;
- var table = this.table;
-
- this.subscriptions.forEach(function (subscription) {
- subscription();
- });
- this.subscriptions = [];
-
- function newData(domainObject, datum) {
- limitEvaluator = telemetryApi.limitEvaluator(domainObject);
- telemetryCollection.add([table.getRowValues(domainObject, limitEvaluator, datum)]);
- }
-
- objects.forEach(function (object) {
- this.subscriptions.push(
- telemetryApi.subscribe(object, newData.bind(this, object), {}));
- }.bind(this));
-
- return objects;
- };
-
- /**
- * Return an array of telemetry objects in this view that should be
- * subscribed to.
- * @private
- * @returns {Promise} a promise that resolves with an array of
- * telemetry objects in this view.
- */
- TelemetryTableController.prototype.getTelemetryObjects = function () {
- var telemetryApi = this.openmct.telemetry;
- var compositionApi = this.openmct.composition;
-
- function filterForTelemetry(objects) {
- return objects.filter(telemetryApi.isTelemetryObject.bind(telemetryApi));
- }
-
- /*
- * If parent object is a telemetry object, subscribe to it. Do not
- * test composees.
- */
- if (telemetryApi.isTelemetryObject(this.domainObject)) {
- return Promise.resolve([this.domainObject]);
- } else {
- /*
- * If parent object is not a telemetry object, subscribe to all
- * composees that are telemetry producing objects.
- */
- var composition = compositionApi.get(this.domainObject);
-
- if (composition) {
- return composition
- .load()
- .then(filterForTelemetry);
- }
- }
- };
-
- /**
- * Request historical data, and subscribe to for real-time data.
- * @private
- * @returns {Promise} A promise that is resolved once subscription is
- * established, and historical telemetry is received and processed.
- */
- TelemetryTableController.prototype.getData = function () {
- var scope = this.$scope;
-
- this.telemetry.clear();
- this.telemetry.bounds(this.openmct.time.bounds());
-
- this.loading(true);
- scope.rows = [];
-
- return this.getTelemetryObjects()
- .then(this.loadColumns)
- .then(this.subscribeToNewData)
- .then(this.getHistoricalData)
- .catch(function error(e) {
- this.loading(false);
- console.error(e.stack || e);
- }.bind(this));
- };
-
- TelemetryTableController.prototype.loading = function (loading) {
- this.$timeout(function () {
- this.$scope.loading = loading;
- }.bind(this));
- };
-
- /**
- * When column configuration changes, update the visible headers
- * accordingly.
- * @private
- */
- 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];
- });
- };
-
- return TelemetryTableController;
- }
-);
diff --git a/platform/features/table/src/directives/MCTTable.js b/platform/features/table/src/directives/MCTTable.js
deleted file mode 100644
index 2ce6d8d8f0..0000000000
--- a/platform/features/table/src/directives/MCTTable.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- "../controllers/MCTTableController",
- "../../res/templates/mct-table.html"
- ],
- function (MCTTableController, TableTemplate) {
- /**
- * Defines a generic 'Table' component. The table can be populated
- * en-masse by setting the rows attribute, or rows can be added as
- * needed via a broadcast 'addRow' event.
- *
- * This directive accepts parameters specifying header and row
- * content, as well as some additional options.
- *
- * Two broadcast events for notifying the table that the rows have
- * changed. For performance reasons, the table does not monitor the
- * content of `rows` constantly.
- * - 'add:row': A $broadcast event that will notify the table that
- * a new row has been added to the table.
- * eg.
- *
- * $scope.rows.push(newRow);
- * $scope.$broadcast('add:row', $scope.rows.length-1);
- *
- * The code above adds a new row, and alerts the table using the
- * add:row event. Sorting and filtering will be applied
- * automatically by the table component.
- *
- * - 'remove:row': A $broadcast event that will notify the table that a
- * row should be removed from the table.
- * eg.
- *
- * $scope.rows.slice(5, 1);
- * $scope.$broadcast('remove:row', 5);
- *
- * The code above removes a row from the rows array, and then alerts
- * the table to its removal.
- *
- * @memberof platform/features/table
- * @param {string[]} headers The column titles to appear at the top
- * of the table. Corresponding values are specified in the rows
- * using the header title provided here.
- * @param {Object[]} rows The row content. Each row is an object
- * with key-value pairs where the key corresponds to a header
- * specified in the headers parameter.
- * @param {boolean} enableFilter If true, values will be searchable
- * and results filtered
- * @param {boolean} enableSort If true, sorting will be enabled
- * allowing sorting by clicking on column headers
- * @param {boolean} autoScroll If true, table will automatically
- * scroll to the bottom as new data arrives. Auto-scroll can be
- * disengaged manually by scrolling away from the bottom of the
- * table, and can also be enabled manually by scrolling to the bottom of
- * the table rows.
- *
- * @constructor
- */
- function MCTTable() {
- return {
- restrict: "E",
- template: TableTemplate,
- controller: [
- '$scope',
- '$window',
- '$element',
- 'exportService',
- 'formatService',
- 'openmct',
- MCTTableController
- ],
- controllerAs: "table",
- scope: {
- headers: "=",
- rows: "=",
- formatCell: "=?",
- enableFilter: "=?",
- enableSort: "=?",
- autoScroll: "=?",
- // Used to indicate which columns contain time data. This
- // will be used for determining when the table is sorted
- // by the column that can be used for time conductor
- // time of interest.
- timeColumns: "=?",
- // Indicate a column to sort on. Allows control of sort
- // via configuration (eg. for default sort column).
- defaultSort: "=?"
- }
- };
- }
-
- return MCTTable;
- }
-);
diff --git a/platform/features/table/test/TableConfigurationSpec.js b/platform/features/table/test/TableConfigurationSpec.js
deleted file mode 100644
index 21402471df..0000000000
--- a/platform/features/table/test/TableConfigurationSpec.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- "../src/TableConfiguration"
- ],
- function (Table) {
-
- describe("A table", function () {
- var mockTableObject,
- mockTelemetryObject,
- mockAPI,
- mockTelemetryAPI,
- table,
- mockTimeAPI,
- mockObjectsAPI,
- mockModel;
-
- beforeEach(function () {
- mockTableObject = jasmine.createSpyObj('domainObject',
- ['getModel', 'useCapability', 'getCapability', 'hasCapability']
- );
- mockModel = {};
- mockTableObject.getModel.and.returnValue(mockModel);
- mockTableObject.getCapability.and.callFake(function (name) {
- return name === 'editor' && {
- isEditContextRoot: function () {
- return true;
- }
- };
- });
- mockTelemetryObject = {
- identifier: {
- namespace: 'mock',
- key: 'domainObject'
- }
- };
-
- mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
- 'getValueFormatter'
- ]);
- mockTimeAPI = jasmine.createSpyObj('timeAPI', [
- 'timeSystem'
- ]);
- mockObjectsAPI = jasmine.createSpyObj('objectsAPI', [
- 'makeKeyString'
- ]);
- mockObjectsAPI.makeKeyString.and.callFake(function (identifier) {
- return [identifier.namespace, identifier.key].join(':');
- });
-
- mockAPI = {
- telemetry: mockTelemetryAPI,
- time: mockTimeAPI,
- objects: mockObjectsAPI
- };
- mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
- var formatter = jasmine.createSpyObj(
- 'telemetryFormatter:' + metadata.key,
- [
- 'format',
- 'parse'
- ]
- );
- var getter = function (datum) {
- return datum[metadata.key];
- };
- formatter.format.and.callFake(getter);
- formatter.parse.and.callFake(getter);
- return formatter;
- });
-
- table = new Table(mockTableObject, mockAPI);
- });
-
- describe("Building columns from telemetry metadata", function () {
- var metadata = [
- {
- name: 'Range 1',
- key: 'range1',
- source: 'range1',
- hints: {
- range: 1
- }
- },
- {
- name: 'Range 2',
- key: 'range2',
- source: 'range2',
- hints: {
- range: 2
- }
- },
- {
- name: 'Domain 1',
- key: 'domain1',
- source: 'domain1',
- format: 'utc',
- hints: {
- domain: 1
- }
- },
- {
- name: 'Domain 2',
- key: 'domain2',
- source: 'domain2',
- format: 'utc',
- hints: {
- domain: 2
- }
- }
- ];
-
- beforeEach(function () {
- mockTimeAPI.timeSystem.and.returnValue({
- key: 'domain1'
- });
- metadata.forEach(function (metadatum) {
- table.addColumn(mockTelemetryObject, metadatum);
- });
- });
-
- it("populates columns", function () {
- expect(table.columns.length).toBe(4);
- });
-
- it("Produces headers for each column based on metadata name", function () {
- expect(table.headers.size).toBe(4);
- Array.from(table.headers.values).forEach(function (header, i) {
- expect(header).toEqual(metadata[i].name);
- });
- });
-
- it("Provides a default configuration with all columns" +
- " visible", function () {
- var configuration = table.buildColumnConfiguration();
-
- expect(configuration).toBeDefined();
- expect(Object.keys(configuration).every(function (key) {
- return configuration[key];
- }));
- });
-
- it("Column configuration exposes persisted configuration", function () {
- var tableConfig,
- modelConfig = {
- table: {
- columns : {
- 'Range 1': false
- }
- }
- };
- mockModel.configuration = modelConfig;
-
- tableConfig = table.buildColumnConfiguration();
-
- expect(tableConfig).toBeDefined();
- expect(tableConfig['Range 1']).toBe(false);
- });
-
- describe('retrieving row values', function () {
- var datum,
- rowValues;
-
- beforeEach(function () {
- datum = {
- 'range1': 10,
- 'range2': 20,
- 'domain1': 0,
- 'domain2': 1
- };
- var limitEvaluator = {
- evaluate: function () {
- return {
- "cssClass": "alarm-class"
- };
- }
- };
- rowValues = table.getRowValues(mockTelemetryObject, limitEvaluator, datum);
- });
-
- it("Returns a value for every column", function () {
- expect(rowValues['Range 1'].text).toEqual(10);
- });
-
- it("Applies appropriate css class if limit violated.", function () {
- expect(rowValues['Range 1'].cssClass).toEqual("alarm-class");
- });
-
- });
- });
- });
- }
-);
diff --git a/platform/features/table/test/TelemetryCollectionSpec.js b/platform/features/table/test/TelemetryCollectionSpec.js
deleted file mode 100644
index 6738ebefff..0000000000
--- a/platform/features/table/test/TelemetryCollectionSpec.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- "../src/TelemetryCollection"
- ],
- function (TelemetryCollection) {
-
- describe("A telemetry collection", function () {
-
- var collection;
- var telemetryObjects;
- var ms;
- var integerTextMap = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE",
- "SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN"];
-
- beforeEach(function () {
- telemetryObjects = [0,9,2,4,7,8,5,1,3,6].map(function (number) {
- ms = number * 1000;
- return {
- timestamp: ms,
- value: {
- integer: number,
- text: integerTextMap[number]
- }
- };
- });
- collection = new TelemetryCollection();
- });
-
- it("Sorts inserted telemetry by specified field",
- function () {
- collection.sort('value.integer');
- collection.add(telemetryObjects);
- expect(collection.telemetry[0].value.integer).toBe(0);
- expect(collection.telemetry[1].value.integer).toBe(1);
- expect(collection.telemetry[2].value.integer).toBe(2);
- expect(collection.telemetry[3].value.integer).toBe(3);
-
- collection.sort('value.text');
- expect(collection.telemetry[0].value.text).toBe("EIGHT");
- expect(collection.telemetry[1].value.text).toBe("FIVE");
- expect(collection.telemetry[2].value.text).toBe("FOUR");
- expect(collection.telemetry[3].value.text).toBe("NINE");
- }
- );
-
- describe("on bounds change", function () {
- var discardedCallback;
-
- beforeEach(function () {
- discardedCallback = jasmine.createSpy("discarded");
- collection.on("discarded", discardedCallback);
- collection.sort("timestamp");
- collection.add(telemetryObjects);
- collection.bounds({start: 5000, end: 8000});
- });
-
-
- it("emits an event indicating that telemetry has " +
- "been discarded", function () {
- expect(discardedCallback).toHaveBeenCalled();
- });
-
- it("discards telemetry data with a time stamp " +
- "before specified start bound", function () {
- var discarded = discardedCallback.calls.mostRecent().args[0];
-
- // Expect 5 because as an optimization, the TelemetryCollection
- // will not consider telemetry values that exceed the upper
- // bounds. Arbitrary bounds changes in which the end bound is
- // decreased is assumed to require a new historical query, and
- // hence re-population of the collection anyway
- expect(discarded.length).toBe(5);
- expect(discarded[0].value.integer).toBe(0);
- expect(discarded[1].value.integer).toBe(1);
- expect(discarded[4].value.integer).toBe(4);
- });
- });
-
- describe("when adding telemetry to a collection", function () {
- var addedCallback;
- beforeEach(function () {
- collection.sort("timestamp");
- collection.add(telemetryObjects);
- addedCallback = jasmine.createSpy("added");
- collection.on("added", addedCallback);
- });
-
- it("emits an event",
- function () {
- var addedObject = {
- timestamp: 10000,
- value: {
- integer: 10,
- text: integerTextMap[10]
- }
- };
- collection.add([addedObject]);
- expect(addedCallback).toHaveBeenCalledWith([addedObject]);
- }
- );
- it("inserts in the correct order",
- function () {
- var addedObjectA = {
- timestamp: 10000,
- value: {
- integer: 10,
- text: integerTextMap[10]
- }
- };
- var addedObjectB = {
- timestamp: 11000,
- value: {
- integer: 11,
- text: integerTextMap[11]
- }
- };
- collection.add([addedObjectB, addedObjectA]);
-
- expect(collection.telemetry[11]).toBe(addedObjectB);
- }
- );
- it("maintains insertion order in the case of duplicate time stamps",
- function () {
- var addedObjectA = {
- timestamp: 10000,
- value: {
- integer: 10,
- text: integerTextMap[10]
- }
- };
- var addedObjectB = {
- timestamp: 10000,
- value: {
- integer: 11,
- text: integerTextMap[11]
- }
- };
- collection.add([addedObjectA, addedObjectB]);
-
- expect(collection.telemetry[11]).toBe(addedObjectB);
- }
- );
- });
-
- describe("buffers telemetry", function () {
- var addedObjectA;
- var addedObjectB;
-
- beforeEach(function () {
- collection.sort("timestamp");
- collection.add(telemetryObjects);
-
- addedObjectA = {
- timestamp: 10000,
- value: {
- integer: 10,
- text: integerTextMap[10]
- }
- };
- addedObjectB = {
- timestamp: 11000,
- value: {
- integer: 11,
- text: integerTextMap[11]
- }
- };
-
- collection.bounds({start: 0, end: 10000});
- collection.add([addedObjectA, addedObjectB]);
- });
- it("when it falls outside of bounds", function () {
- expect(collection.highBuffer).toBeDefined();
- expect(collection.highBuffer.length).toBe(1);
- expect(collection.highBuffer[0]).toBe(addedObjectB);
- });
- it("and adds it to collection when it falls within bounds", function () {
- expect(collection.telemetry.length).toBe(11);
- collection.bounds({start: 0, end: 11000});
- expect(collection.telemetry.length).toBe(12);
- expect(collection.telemetry[11]).toBe(addedObjectB);
- });
- it("and removes it from the buffer when it falls within bounds", function () {
- expect(collection.highBuffer.length).toBe(1);
- collection.bounds({start: 0, end: 11000});
- expect(collection.highBuffer.length).toBe(0);
- });
- });
- });
- }
-);
diff --git a/platform/features/table/test/controllers/MCTTableControllerSpec.js b/platform/features/table/test/controllers/MCTTableControllerSpec.js
deleted file mode 100644
index f004bd69fa..0000000000
--- a/platform/features/table/test/controllers/MCTTableControllerSpec.js
+++ /dev/null
@@ -1,598 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- "zepto",
- "moment",
- "../../src/controllers/MCTTableController"
- ],
- function ($, moment, MCTTableController) {
-
- var MOCK_ELEMENT_TEMPLATE =
- '';
-
- describe('The MCTTable Controller', function () {
-
- var controller,
- mockScope,
- watches,
- mockWindow,
- mockElement,
- mockExportService,
- mockConductor,
- mockFormatService,
- mockFormat;
-
- function getCallback(target, event) {
- return target.calls.all().filter(function (call) {
- return call.args[0] === event;
- })[0].args[1];
- }
-
- beforeEach(function () {
- watches = {};
-
- mockScope = jasmine.createSpyObj('scope', [
- '$watch',
- '$on',
- '$watchCollection',
- '$digest'
- ]);
- mockScope.$watchCollection.and.callFake(function (event, callback) {
- watches[event] = callback;
- });
-
- mockElement = $(MOCK_ELEMENT_TEMPLATE);
- mockExportService = jasmine.createSpyObj('exportService', [
- 'exportCSV'
- ]);
-
- mockConductor = jasmine.createSpyObj('conductor', [
- 'bounds',
- 'timeOfInterest',
- 'timeSystem',
- 'on',
- 'off'
- ]);
-
- mockScope.displayHeaders = true;
- mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
- mockWindow.requestAnimationFrame.and.callFake(function (f) {
- return f();
- });
-
- mockFormat = jasmine.createSpyObj('formatter', [
- 'parse',
- 'format'
- ]);
- mockFormatService = jasmine.createSpyObj('formatService', [
- 'getFormat'
- ]);
- mockFormatService.getFormat.and.returnValue(mockFormat);
-
- controller = new MCTTableController(
- mockScope,
- mockWindow,
- mockElement,
- mockExportService,
- mockFormatService,
- {time: mockConductor}
- );
- spyOn(controller, 'setVisibleRows').and.callThrough();
- });
-
- it('Reacts to changes to filters, headers, and rows', function () {
- expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
- expect(mockScope.$watch).toHaveBeenCalledWith('headers', jasmine.any(Function));
- expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
- });
-
- it('unregisters listeners on destruction', function () {
- expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
- getCallback(mockScope.$on, '$destroy')();
-
- expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
- expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest);
- expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
- });
-
- describe('The time of interest', function () {
- var rowsAsc = [];
- var rowsDesc = [];
- beforeEach(function () {
- rowsAsc = [
- {
- 'col1': {'text': 'row1 col1 match'},
- 'col2': {'text': '2012-10-31 00:00:00.000Z'},
- 'col3': {'text': 'row1 col3'}
- },
- {
- 'col1': {'text': 'row2 col1 match'},
- 'col2': {'text': '2012-11-01 00:00:00.000Z'},
- 'col3': {'text': 'row2 col3'}
- },
- {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': '2012-11-03 00:00:00.000Z'},
- 'col3': {'text': 'row3 col3'}
- },
- {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': '2012-11-04 00:00:00.000Z'},
- 'col3': {'text': 'row3 col3'}
- }
- ];
- rowsDesc = [
- {
- 'col1': {'text': 'row1 col1 match'},
- 'col2': {'text': '2012-11-02 00:00:00.000Z'},
- 'col3': {'text': 'row1 col3'}
- },
- {
- 'col1': {'text': 'row2 col1 match'},
- 'col2': {'text': '2012-11-01 00:00:00.000Z'},
- 'col3': {'text': 'row2 col3'}
- },
- {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': '2012-10-30 00:00:00.000Z'},
- 'col3': {'text': 'row3 col3'}
- },
- {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': '2012-10-29 00:00:00.000Z'},
- 'col3': {'text': 'row3 col3'}
- }
- ];
- mockScope.timeColumns = ['col2'];
- mockScope.sortColumn = 'col2';
- controller.toiFormatter = mockFormat;
- });
- it("is observed for changes", function () {
- //Mock setting time columns
- getCallback(mockScope.$watch, 'timeColumns')(['col2']);
-
- expect(mockConductor.on).toHaveBeenCalledWith('timeOfInterest',
- jasmine.any(Function));
- });
- describe("causes corresponding row to be highlighted", function () {
- it("when changed and rows sorted ascending", function () {
- var testDate = "2012-11-02 00:00:00.000Z";
- mockScope.rows = rowsAsc;
- mockScope.displayRows = rowsAsc;
- mockScope.sortDirection = 'asc';
-
- var toi = moment.utc(testDate).valueOf();
- mockFormat.parse.and.returnValue(toi);
- mockFormat.format.and.returnValue(testDate);
-
- //mock setting the timeColumns parameter
- getCallback(mockScope.$watch, 'timeColumns')(['col2']);
-
- var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
- toiCallback(toi);
-
- expect(mockScope.toiRowIndex).toBe(2);
- });
- it("when changed and rows sorted descending", function () {
- var testDate = "2012-10-31 00:00:00.000Z";
- mockScope.rows = rowsDesc;
- mockScope.displayRows = rowsDesc;
- mockScope.sortDirection = 'desc';
-
- var toi = moment.utc(testDate).valueOf();
- mockFormat.parse.and.returnValue(toi);
- mockFormat.format.and.returnValue(testDate);
-
- //mock setting the timeColumns parameter
- getCallback(mockScope.$watch, 'timeColumns')(['col2']);
-
- var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
- toiCallback(toi);
-
- expect(mockScope.toiRowIndex).toBe(2);
- });
- it("when rows are set and sorted ascending", function () {
- var testDate = "2012-11-02 00:00:00.000Z";
- mockScope.sortDirection = 'asc';
-
- var toi = moment.utc(testDate).valueOf();
- mockFormat.parse.and.returnValue(toi);
- mockFormat.format.and.returnValue(testDate);
- mockConductor.timeOfInterest.and.returnValue(toi);
-
- //mock setting the timeColumns parameter
- getCallback(mockScope.$watch, 'timeColumns')(['col2']);
-
- //Mock setting the rows on scope
- var rowsCallback = getCallback(mockScope.$watch, 'rows');
- var setRowsPromise = rowsCallback(rowsAsc);
-
- return setRowsPromise.then(function () {
- expect(mockScope.toiRowIndex).toBe(2);
- });
- });
-
- });
- });
-
- describe('rows', function () {
- var testRows = [];
- beforeEach(function () {
- testRows = [
- {
- 'col1': {'text': 'row1 col1 match'},
- 'col2': {'text': 'def'},
- 'col3': {'text': 'row1 col3'}
- },
- {
- 'col1': {'text': 'row2 col1 match'},
- 'col2': {'text': 'abc'},
- 'col3': {'text': 'row2 col3'}
- },
- {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': 'ghi'},
- 'col3': {'text': 'row3 col3'}
- }
- ];
- mockScope.rows = testRows;
- });
-
- it('Filters results based on filter input', function () {
- var filters = {},
- filteredRows;
-
- mockScope.filters = filters;
-
- filteredRows = controller.filterRows(testRows);
- expect(filteredRows.length).toBe(3);
- filters.col1 = 'row1';
- filteredRows = controller.filterRows(testRows);
- expect(filteredRows.length).toBe(1);
- filters.col1 = 'match';
- filteredRows = controller.filterRows(testRows);
- expect(filteredRows.length).toBe(2);
- });
-
- it('Sets rows on scope when rows change', function () {
- controller.setRows(testRows);
- expect(mockScope.displayRows.length).toBe(3);
- expect(mockScope.displayRows).toEqual(testRows);
- });
-
- it('Supports adding rows individually', function () {
- var addRowFunc = getCallback(mockScope.$on, 'add:rows'),
- row4 = {
- 'col1': {'text': 'row3 col1'},
- 'col2': {'text': 'ghi'},
- 'col3': {'text': 'row3 col3'}
- };
- controller.setRows(testRows);
- expect(mockScope.displayRows.length).toBe(3);
- testRows.push(row4);
- addRowFunc(undefined, [row4]);
- expect(mockScope.displayRows.length).toBe(4);
- });
-
- it('Supports removing rows individually', function () {
- var removeRowFunc = getCallback(mockScope.$on, 'remove:rows');
- controller.setRows(testRows);
- expect(mockScope.displayRows.length).toBe(3);
- removeRowFunc(undefined, [testRows[2]]);
- expect(mockScope.displayRows.length).toBe(2);
- expect(controller.setVisibleRows).toHaveBeenCalled();
- });
-
- it("can be exported as CSV", function () {
- controller.setRows(testRows);
- controller.setHeaders(Object.keys(testRows[0]));
- mockScope.exportAsCSV();
- expect(mockExportService.exportCSV)
- .toHaveBeenCalled();
- mockExportService.exportCSV.calls.mostRecent().args[0]
- .forEach(function (row, i) {
- Object.keys(row).forEach(function (k) {
- expect(row[k]).toEqual(
- mockScope.displayRows[i][k].text
- );
- });
- });
- });
-
- describe('sorting', function () {
- var sortedRows;
-
- beforeEach(function () {
- sortedRows = [];
- });
-
- it('Sorts rows ascending', function () {
- mockScope.sortColumn = 'col1';
- mockScope.sortDirection = 'asc';
-
- sortedRows = controller.sortRows(testRows);
- expect(sortedRows[0].col1.text).toEqual('row1 col1 match');
- expect(sortedRows[1].col1.text).toEqual('row2 col1' +
- ' match');
- expect(sortedRows[2].col1.text).toEqual('row3 col1');
-
- });
-
- it('Sorts rows descending', function () {
- mockScope.sortColumn = 'col1';
- mockScope.sortDirection = 'desc';
-
- sortedRows = controller.sortRows(testRows);
- expect(sortedRows[0].col1.text).toEqual('row3 col1');
- expect(sortedRows[1].col1.text).toEqual('row2 col1 match');
- expect(sortedRows[2].col1.text).toEqual('row1 col1 match');
- });
- it('Sorts rows descending based on selected sort column', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'desc';
-
- sortedRows = controller.sortRows(testRows);
- expect(sortedRows[0].col2.text).toEqual('ghi');
- expect(sortedRows[1].col2.text).toEqual('def');
- expect(sortedRows[2].col2.text).toEqual('abc');
- });
-
- it('Allows sort column to be changed externally by ' +
- 'setting or changing sortBy attribute', function () {
- mockScope.displayRows = testRows;
- var sortByCB = getCallback(mockScope.$watch, 'defaultSort');
- sortByCB('col2');
-
- expect(mockScope.sortDirection).toEqual('asc');
-
- expect(mockScope.displayRows[0].col2.text).toEqual('abc');
- expect(mockScope.displayRows[1].col2.text).toEqual('def');
- expect(mockScope.displayRows[2].col2.text).toEqual('ghi');
-
- });
-
- // https://github.com/nasa/openmct/issues/910
- it('updates visible rows in scope', function () {
- var oldRows;
- mockScope.rows = testRows;
- var setRowsPromise = controller.setRows(testRows);
-
- oldRows = mockScope.visibleRows;
- mockScope.toggleSort('col2');
-
- return setRowsPromise.then(function () {
- expect(mockScope.visibleRows).not.toEqual(oldRows);
- });
- });
-
- it('correctly sorts rows of differing types', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'desc';
-
- testRows.push({
- 'col1': {'text': 'row4 col1'},
- 'col2': {'text': '123'},
- 'col3': {'text': 'row4 col3'}
- });
- testRows.push({
- 'col1': {'text': 'row5 col1'},
- 'col2': {'text': '456'},
- 'col3': {'text': 'row5 col3'}
- });
- testRows.push({
- 'col1': {'text': 'row5 col1'},
- 'col2': {'text': ''},
- 'col3': {'text': 'row5 col3'}
- });
-
- sortedRows = controller.sortRows(testRows);
- expect(sortedRows[0].col2.text).toEqual('ghi');
- expect(sortedRows[1].col2.text).toEqual('def');
- expect(sortedRows[2].col2.text).toEqual('abc');
-
- expect(sortedRows[sortedRows.length - 3].col2.text).toEqual('456');
- expect(sortedRows[sortedRows.length - 2].col2.text).toEqual('123');
- expect(sortedRows[sortedRows.length - 1].col2.text).toEqual('');
- });
-
- describe('The sort comparator', function () {
- it('Correctly sorts different data types', function () {
- var val1 = "",
- val2 = "1",
- val3 = "2016-04-05 18:41:30.713Z",
- val4 = "1.1",
- val5 = "8.945520958175627e-13";
- mockScope.sortDirection = "asc";
-
- expect(controller.sortComparator(val1, val2)).toEqual(-1);
- expect(controller.sortComparator(val3, val1)).toEqual(1);
- expect(controller.sortComparator(val3, val2)).toEqual(1);
- expect(controller.sortComparator(val4, val2)).toEqual(1);
- expect(controller.sortComparator(val2, val5)).toEqual(1);
- });
- });
-
- describe('Adding new rows', function () {
- var row4,
- row5,
- row6;
-
- beforeEach(function () {
- row4 = {
- 'col1': {'text': 'row4 col1'},
- 'col2': {'text': 'xyz'},
- 'col3': {'text': 'row4 col3'}
- };
- row5 = {
- 'col1': {'text': 'row5 col1'},
- 'col2': {'text': 'aaa'},
- 'col3': {'text': 'row5 col3'}
- };
- row6 = {
- 'col1': {'text': 'row6 col1'},
- 'col2': {'text': 'ggg'},
- 'col3': {'text': 'row6 col3'}
- };
- });
-
- it('Adds new rows at the correct sort position when' +
- ' sorted ', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'desc';
-
- mockScope.displayRows = controller.sortRows(testRows.slice(0));
-
- controller.addRows(undefined, [row4, row5, row6, row6]);
- expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
- expect(mockScope.displayRows[6].col2.text).toEqual('aaa');
- //Added a duplicate row
- expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
- expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
- });
-
- it('Inserts duplicate values for sort column in order received when sorted descending', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'desc';
-
- mockScope.displayRows = controller.sortRows(testRows.slice(0));
-
- var row6b = {
- 'col1': {'text': 'row6b col1'},
- 'col2': {'text': 'ggg'},
- 'col3': {'text': 'row6b col3'}
- };
- var row6c = {
- 'col1': {'text': 'row6c col1'},
- 'col2': {'text': 'ggg'},
- 'col3': {'text': 'row6c col3'}
- };
-
- controller.addRows(undefined, [row4, row5]);
- controller.addRows(undefined, [row6, row6b, row6c]);
- expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
- expect(mockScope.displayRows[7].col2.text).toEqual('aaa');
-
- // Added duplicate rows
- expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
- expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
- expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
-
- // Check that original order is maintained with dupes
- expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3');
- expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3');
- expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3');
- });
-
- it('Inserts duplicate values for sort column in order received when sorted ascending', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'asc';
-
- mockScope.displayRows = controller.sortRows(testRows.slice(0));
-
- var row6b = {
- 'col1': {'text': 'row6b col1'},
- 'col2': {'text': 'ggg'},
- 'col3': {'text': 'row6b col3'}
- };
- var row6c = {
- 'col1': {'text': 'row6c col1'},
- 'col2': {'text': 'ggg'},
- 'col3': {'text': 'row6c col3'}
- };
-
- controller.addRows(undefined, [row4, row5, row6]);
- controller.addRows(undefined, [row6b, row6c]);
- expect(mockScope.displayRows[0].col2.text).toEqual('aaa');
- expect(mockScope.displayRows[7].col2.text).toEqual('xyz');
-
- // Added duplicate rows
- expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
- expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
- expect(mockScope.displayRows[5].col2.text).toEqual('ggg');
- // Check that original order is maintained with dupes
- expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3');
- expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3');
- expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3');
- });
-
- it('Adds new rows at the correct sort position when' +
- ' sorted and filtered', function () {
- mockScope.sortColumn = 'col2';
- mockScope.sortDirection = 'desc';
- mockScope.filters = {'col2': 'a'};//Include only
- // rows with 'a'
-
- mockScope.displayRows = controller.sortRows(testRows.slice(0));
- mockScope.displayRows = controller.filterRows(testRows);
-
- controller.addRows(undefined, [row5]);
- expect(mockScope.displayRows.length).toBe(2);
- expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
-
- controller.addRows(undefined, [row6]);
- expect(mockScope.displayRows.length).toBe(2);
- //Row was not added because does not match filter
- });
-
- it('Adds new rows at the correct sort position when' +
- ' not sorted ', function () {
- mockScope.sortColumn = undefined;
- mockScope.sortDirection = undefined;
- mockScope.filters = {};
-
- mockScope.displayRows = testRows.slice(0);
-
- controller.addRows(undefined, [row5]);
- expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
-
- controller.addRows(undefined, [row6]);
- expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
- });
-
- it('Resizes columns if length of any columns in new' +
- ' row exceeds corresponding existing column', function () {
- var row7 = {
- 'col1': {'text': 'row6 col1'},
- 'col2': {'text': 'some longer string'},
- 'col3': {'text': 'row6 col3'}
- };
-
- mockScope.sortColumn = undefined;
- mockScope.sortDirection = undefined;
- mockScope.filters = {};
-
- mockScope.displayRows = testRows.slice(0);
-
- controller.addRows(undefined, [row7]);
- expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
- });
-
- });
- });
- });
- });
- });
diff --git a/platform/features/table/test/controllers/TableOptionsControllerSpec.js b/platform/features/table/test/controllers/TableOptionsControllerSpec.js
deleted file mode 100644
index f88b26a428..0000000000
--- a/platform/features/table/test/controllers/TableOptionsControllerSpec.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- "../../src/controllers/TableOptionsController"
- ],
- function (TableOptionsController) {
-
- describe('The Table Options Controller', function () {
- var mockDomainObject,
- mockCapability,
- controller,
- mockScope;
-
- beforeEach(function () {
- mockCapability = jasmine.createSpyObj('mutationCapability', [
- 'listen'
- ]);
- mockDomainObject = jasmine.createSpyObj('domainObject', [
- 'getCapability',
- 'getModel'
- ]);
- mockDomainObject.getCapability.and.returnValue(mockCapability);
- mockDomainObject.getModel.and.returnValue({});
-
- mockScope = jasmine.createSpyObj('scope', [
- '$watchCollection',
- '$watch',
- '$on'
- ]);
- mockScope.domainObject = mockDomainObject;
-
- controller = new TableOptionsController(mockScope);
- });
-
- it('Listens for changing domain object', function () {
- expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
- });
-
- it('On destruction of controller, destroys listeners', function () {
- var unlistenFunc = jasmine.createSpy("unlisten");
- controller.listeners.push(unlistenFunc);
- expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
- mockScope.$on.calls.mostRecent().args[1]();
- expect(unlistenFunc).toHaveBeenCalled();
- });
-
- it('Registers a listener for mutation events on the object', function () {
- mockScope.$watch.calls.mostRecent().args[1](mockDomainObject);
- expect(mockCapability.listen).toHaveBeenCalled();
- });
-
- it('Listens for changes to object composition and updates' +
- ' options accordingly', function () {
- expect(mockScope.$watchCollection).toHaveBeenCalledWith('configuration.table.columns', jasmine.any(Function));
- });
-
- describe('Populates scope with a form definition based on provided' +
- ' column configuration', function () {
- var mockModel;
-
- beforeEach(function () {
- mockModel = {
- configuration: {
- table: {
- columns: {
- 'column1': true,
- 'column2': true,
- 'column3': false,
- 'column4': true
- }
- }
- }
- };
- controller.populateForm(mockModel);
- });
-
- it('creates form on scope', function () {
- expect(mockScope.columnsForm).toBeDefined();
- expect(mockScope.columnsForm.sections[0]).toBeDefined();
- expect(mockScope.columnsForm.sections[0].rows).toBeDefined();
- expect(mockScope.columnsForm.sections[0].rows.length).toBe(4);
- });
-
- it('presents columns as checkboxes', function () {
- expect(mockScope.columnsForm.sections[0].rows.every(function (row) {
- return row.control === 'checkbox';
- })).toBe(true);
- });
- });
- });
-
- });
diff --git a/platform/features/table/test/controllers/TelemetryTableControllerSpec.js b/platform/features/table/test/controllers/TelemetryTableControllerSpec.js
deleted file mode 100644
index bb52d3153f..0000000000
--- a/platform/features/table/test/controllers/TelemetryTableControllerSpec.js
+++ /dev/null
@@ -1,417 +0,0 @@
-/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2018, 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(
- [
- '../../src/controllers/TelemetryTableController',
- '../../../../../src/api/objects/object-utils',
- 'lodash'
- ],
- function (TelemetryTableController, objectUtils, _) {
-
- describe('The TelemetryTableController', function () {
-
- var controller,
- mockScope,
- mockTimeout,
- mockConductor,
- mockAPI,
- mockDomainObject,
- mockTelemetryAPI,
- mockObjectAPI,
- mockCompositionAPI,
- unobserve,
- mockBounds;
-
- function getCallback(target, event) {
- return target.calls.all().filter(function (call) {
- return call.args[0] === event;
- })[0].args[1];
- }
-
- beforeEach(function () {
- mockBounds = {
- start: 0,
- end: 10
- };
- mockConductor = jasmine.createSpyObj("conductor", [
- "bounds",
- "clock",
- "on",
- "off",
- "timeSystem"
- ]);
- mockConductor.bounds.and.returnValue(mockBounds);
- mockConductor.clock.and.returnValue(undefined);
-
- mockDomainObject = jasmine.createSpyObj("domainObject", [
- "getModel",
- "getId",
- "useCapability",
- "hasCapability"
- ]);
- mockDomainObject.getModel.and.returnValue({});
- mockDomainObject.getId.and.returnValue("mockId");
- mockDomainObject.useCapability.and.returnValue(true);
-
- mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [
- "get"
- ]);
-
- mockObjectAPI = jasmine.createSpyObj("objectAPI", [
- "observe",
- "makeKeyString"
- ]);
- unobserve = jasmine.createSpy("unobserve");
- mockObjectAPI.observe.and.returnValue(unobserve);
-
- mockScope = jasmine.createSpyObj("scope", [
- "$on",
- "$watch",
- "$broadcast"
- ]);
- mockScope.domainObject = mockDomainObject;
-
- mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
- "isTelemetryObject",
- "subscribe",
- "getMetadata",
- "commonValuesForHints",
- "request",
- "limitEvaluator",
- "getValueFormatter"
- ]);
- mockTelemetryAPI.commonValuesForHints.and.returnValue([]);
- mockTelemetryAPI.request.and.returnValue(Promise.resolve([]));
- mockTelemetryAPI.getMetadata.and.returnValue({
- values: function () {
- return [];
- }
- });
- mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
- var formatter = jasmine.createSpyObj(
- 'telemetryFormatter:' + metadata.key,
- [
- 'format',
- 'parse'
- ]
- );
- var getter = function (datum) {
- return datum[metadata.key];
- };
- formatter.format.and.callFake(getter);
- formatter.parse.and.callFake(getter);
- return formatter;
- });
-
- mockTelemetryAPI.isTelemetryObject.and.returnValue(false);
-
- mockTimeout = jasmine.createSpy("timeout");
- mockTimeout.and.returnValue(1); // Return something
- mockTimeout.cancel = jasmine.createSpy("cancel");
-
- mockAPI = {
- time: mockConductor,
- objects: mockObjectAPI,
- telemetry: mockTelemetryAPI,
- composition: mockCompositionAPI
- };
- controller = new TelemetryTableController(mockScope, mockTimeout, mockAPI);
- });
-
- describe('listens for', function () {
- beforeEach(function () {
- controller.registerChangeListeners();
- });
- it('object mutation', function () {
- var calledObject = mockObjectAPI.observe.calls.mostRecent().args[0];
-
- expect(mockObjectAPI.observe).toHaveBeenCalled();
- expect(calledObject.identifier.key).toEqual(mockDomainObject.getId());
- });
- it('conductor changes', function () {
- expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
- expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
- expect(mockConductor.on).toHaveBeenCalledWith("clock", jasmine.any(Function));
- });
- });
-
- describe('deregisters all listeners on scope destruction', function () {
- var timeSystemListener,
- boundsListener,
- clockListener;
-
- beforeEach(function () {
- controller.registerChangeListeners();
-
- timeSystemListener = getCallback(mockConductor.on, "timeSystem");
- boundsListener = getCallback(mockConductor.on, "bounds");
- clockListener = getCallback(mockConductor.on, "clock");
-
- var destroy = getCallback(mockScope.$on, "$destroy");
- destroy();
- });
-
- it('object mutation', function () {
- expect(unobserve).toHaveBeenCalled();
- });
- it('conductor changes', function () {
- expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener);
- expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener);
- expect(mockConductor.off).toHaveBeenCalledWith("clock", clockListener);
- });
- });
-
- describe ('when getting telemetry', function () {
- var mockComposition,
- mockTelemetryObject,
- mockChildren,
- unsubscribe;
-
- beforeEach(function () {
- mockComposition = jasmine.createSpyObj("composition", [
- "load"
- ]);
-
- mockTelemetryObject = {};
- mockTelemetryObject.identifier = {
- key: "mockTelemetryObject"
- };
-
- unsubscribe = jasmine.createSpy("unsubscribe");
- mockTelemetryAPI.subscribe.and.returnValue(unsubscribe);
-
- mockChildren = [mockTelemetryObject];
- mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
- mockCompositionAPI.get.and.returnValue(mockComposition);
-
- mockTelemetryAPI.isTelemetryObject.and.callFake(function (obj) {
- return obj.identifier.key === mockTelemetryObject.identifier.key;
- });
- });
-
- it('fetches historical data for the time period specified by the conductor bounds', function () {
- return controller.getData().then(function () {
- expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
- });
- });
-
- it('unsubscribes on view destruction', function () {
- return controller.getData().then(function () {
- var destroy = getCallback(mockScope.$on, "$destroy");
- destroy();
-
- expect(unsubscribe).toHaveBeenCalled();
- });
- });
- it('fetches historical data for the time period specified by the conductor bounds', function () {
- return controller.getData().then(function () {
- expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
- });
- });
-
- it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
- return controller.getData().then(function () {
- expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
- expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
- });
- });
- it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
- return controller.getData().then(function () {
- expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
- expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
- });
- });
-
- it('fetches data for, and subscribes to any composees that are telemetry objects if parent is not', function () {
- mockChildren = [
- {name: "child 1"}
- ];
- var mockTelemetryChildren = [
- {name: "child 2"},
- {name: "child 3"},
- {name: "child 4"}
- ];
- mockChildren = mockChildren.concat(mockTelemetryChildren);
- mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
-
- mockTelemetryAPI.isTelemetryObject.and.callFake(function (object) {
- if (object === mockTelemetryObject) {
- return false;
- } else {
- return mockTelemetryChildren.indexOf(object) !== -1;
- }
- });
-
- return controller.getData().then(function () {
- mockTelemetryChildren.forEach(function (child) {
- expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(child, jasmine.any(Function), {});
- });
-
- mockTelemetryChildren.forEach(function (child) {
- expect(mockTelemetryAPI.request).toHaveBeenCalledWith(child, jasmine.any(Object));
- });
-
- expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockChildren[0], jasmine.any(Function), {});
- expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockTelemetryObject[0], jasmine.any(Function), {});
- });
- });
- });
-
- it('When in real-time mode, enables auto-scroll', function () {
- controller.registerChangeListeners();
-
- var clockCallback = getCallback(mockConductor.on, "clock");
- //Confirm pre-condition
- expect(mockScope.autoScroll).toBeFalsy();
-
- //Mock setting the a clock in the Time API
- clockCallback({});
- expect(mockScope.autoScroll).toBe(true);
- });
-
- describe('populates table columns', function () {
- var allMetadata;
- var mockTimeSystem1;
- var mockTimeSystem2;
-
- beforeEach(function () {
- allMetadata = [{
- key: "column1",
- name: "Column 1",
- hints: {
- domain: 1
- }
- }, {
- key: "column2",
- name: "Column 2",
- hints: {
- domain: 2
- }
- }, {
- key: "column3",
- name: "Column 3",
- hints: {}
- }];
-
- mockTimeSystem1 = {
- key: "column1"
- };
- mockTimeSystem2 = {
- key: "column2"
- };
-
- mockConductor.timeSystem.and.returnValue(mockTimeSystem1);
-
- mockTelemetryAPI.getMetadata.and.returnValue({
- values: function () {
- return allMetadata;
- }
- });
-
- controller.loadColumns([mockDomainObject]);
- });
-
- it('based on metadata for given objects', function () {
- expect(mockScope.headers).toBeDefined();
- expect(mockScope.headers.length).toBeGreaterThan(0);
- expect(mockScope.headers.indexOf(allMetadata[0].name)).not.toBe(-1);
- expect(mockScope.headers.indexOf(allMetadata[1].name)).not.toBe(-1);
- expect(mockScope.headers.indexOf(allMetadata[2].name)).not.toBe(-1);
- });
-
- it('and sorts by column matching time system', function () {
- expect(mockScope.defaultSort).toEqual("Column 1");
-
- mockConductor.timeSystem.and.returnValue(mockTimeSystem2);
- controller.sortByTimeSystem();
-
- expect(mockScope.defaultSort).toEqual("Column 2");
- });
-
- it('batches processing of rows for performance when receiving historical telemetry', function () {
- var mockHistoricalData = [
- {
- "column1": 1,
- "column2": 2,
- "column3": 3
- },{
- "column1": 4,
- "column2": 5,
- "column3": 6
- }, {
- "column1": 7,
- "column2": 8,
- "column3": 9
- }
- ];
-
- controller.batchSize = 2;
- mockTelemetryAPI.request.and.returnValue(Promise.resolve(mockHistoricalData));
- controller.getHistoricalData([mockDomainObject]);
-
- return new Promise(function (resolve) {
- mockTimeout.and.callFake(function () {
- resolve();
- });
- }).then(function () {
- mockTimeout.calls.mostRecent().args[0]();
- expect(mockTimeout.calls.count()).toBe(2);
- mockTimeout.calls.mostRecent().args[0]();
- expect(mockScope.rows.length).toBe(3);
-
- });
- });
- });
-
- it('Removes telemetry rows from table when they fall out of bounds', function () {
- var discardedRows = [
- {"column1": "value 1"},
- {"column2": "value 2"},
- {"column3": "value 3"}
- ];
-
- spyOn(controller.telemetry, "on").and.callThrough();
-
- controller.registerChangeListeners();
- expect(controller.telemetry.on).toHaveBeenCalledWith("discarded", jasmine.any(Function));
- var onDiscard = getCallback(controller.telemetry.on, "discarded");
- onDiscard(discardedRows);
- expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
- });
-
- describe('when telemetry is added', function () {
- var testRows;
-
- beforeEach(function () {
- testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
-
- controller.registerChangeListeners();
- controller.telemetry.add(testRows);
- });
-
- it("Adds the rows to the MCTTable directive", function () {
- expect(mockScope.$broadcast).toHaveBeenCalledWith("add:rows", testRows);
- });
- });
- });
- });
diff --git a/src/defaultRegistry.js b/src/defaultRegistry.js
index a9d97ed564..c75cd3f0b2 100644
--- a/src/defaultRegistry.js
+++ b/src/defaultRegistry.js
@@ -64,7 +64,6 @@ define([
'../platform/features/pages/bundle',
'../platform/features/hyperlink/bundle',
'../platform/features/static-markup/bundle',
- '../platform/features/table/bundle',
'../platform/features/timeline/bundle',
'../platform/forms/bundle',
'../platform/framework/bundle',
@@ -108,7 +107,6 @@ define([
'platform/features/pages',
'platform/features/hyperlink',
'platform/features/timeline',
- 'platform/features/table',
'platform/forms',
'platform/identity',
'platform/persistence/aggregator',
diff --git a/src/exporters/CSVExporter.js b/src/exporters/CSVExporter.js
new file mode 100644
index 0000000000..284d38fea3
--- /dev/null
+++ b/src/exporters/CSVExporter.js
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ 'csv',
+ 'saveAs'
+], function (CSV, saveAs) {
+ class CSVExporter {
+ export(rows, options) {
+ let headers = (options && options.headers) ||
+ (Object.keys((rows[0] || {})).sort());
+ let filename = (options && options.filename) || "export.csv";
+ let csvText = new CSV(rows, { header: headers }).encode();
+ let blob = new Blob([csvText], { type: "text/csv" });
+ saveAs(blob, filename);
+ }
+ }
+
+ return CSVExporter;
+});
\ No newline at end of file
diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js
index e7df3bfb7e..e404e8ebca 100644
--- a/src/plugins/plugins.js
+++ b/src/plugins/plugins.js
@@ -33,6 +33,7 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
+ './telemetryTable/plugin',
'./staticRootPlugin/plugin'
], function (
_,
@@ -47,6 +48,7 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
+ TelemetryTablePlugin,
StaticRootPlugin
) {
var bundleMap = {
@@ -152,7 +154,8 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.Plot = PlotPlugin;
-
+ plugins.TelemetryTable = TelemetryTablePlugin;
+
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
diff --git a/src/plugins/telemetryTable/TableConfigurationComponent.js b/src/plugins/telemetryTable/TableConfigurationComponent.js
new file mode 100644
index 0000000000..9f8f60892f
--- /dev/null
+++ b/src/plugins/telemetryTable/TableConfigurationComponent.js
@@ -0,0 +1,87 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ 'lodash',
+ 'vue',
+ './table-configuration.html',
+ './TelemetryTableConfiguration'
+],function (
+ _,
+ Vue,
+ TableConfigurationTemplate,
+ TelemetryTableConfiguration
+) {
+ return function TableConfigurationComponent(domainObject, openmct) {
+ const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
+ let unlisteners = [];
+
+ return new Vue({
+ template: TableConfigurationTemplate,
+ data() {
+ return {
+ headers: {},
+ configuration: tableConfiguration.getConfiguration()
+ }
+ },
+ methods: {
+ updateHeaders(headers) {
+ this.headers = headers;
+ },
+ toggleColumn(key) {
+ let isHidden = this.configuration.hiddenColumns[key] === true;
+
+ this.configuration.hiddenColumns[key] = !isHidden;
+ tableConfiguration.updateConfiguration(this.configuration);
+ },
+ addObject(domainObject) {
+ tableConfiguration.addColumnsForObject(domainObject, true);
+ this.updateHeaders(tableConfiguration.getAllHeaders());
+ },
+ removeObject(objectIdentifier) {
+ tableConfiguration.removeColumnsForObject(objectIdentifier, true);
+ this.updateHeaders(tableConfiguration.getAllHeaders());
+ }
+
+ },
+ mounted() {
+ let compositionCollection = openmct.composition.get(domainObject);
+
+ compositionCollection.load()
+ .then((composition) => {
+ tableConfiguration.addColumnsForAllObjects(composition);
+ this.updateHeaders(tableConfiguration.getAllHeaders());
+
+ compositionCollection.on('add', this.addObject);
+ unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
+
+ compositionCollection.on('remove', this.removeObject);
+ unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
+ });
+ },
+ destroyed() {
+ tableConfiguration.destroy();
+ unlisteners.forEach((unlisten) => unlisten());
+ }
+ });
+ }
+ });
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TableConfigurationViewProvider.js b/src/plugins/telemetryTable/TableConfigurationViewProvider.js
new file mode 100644
index 0000000000..1203160c6c
--- /dev/null
+++ b/src/plugins/telemetryTable/TableConfigurationViewProvider.js
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ '../../api/objects/object-utils',
+ './TableConfigurationComponent'
+], function (
+ objectUtils,
+ TableConfigurationComponent
+) {
+ function TableConfigurationViewProvider(openmct) {
+ let instantiateService;
+
+ function isBeingEdited(object) {
+ let oldStyleObject = getOldStyleObject(object);
+
+ return oldStyleObject.hasCapability('editor') &&
+ oldStyleObject.getCapability('editor').inEditContext();
+ }
+
+ function getOldStyleObject(object) {
+ let oldFormatModel = objectUtils.toOldFormat(object);
+ let oldFormatId = objectUtils.makeKeyString(object.identifier);
+
+ return instantiate(oldFormatModel, oldFormatId);
+ }
+
+ function instantiate(model, id) {
+ if (!instantiateService) {
+ instantiateService = openmct.$injector.get('instantiate');
+ }
+ return instantiateService(model, id);
+ }
+
+ return {
+ key: 'table-configuration',
+ name: 'Telemetry Table Configuration',
+ canView: function (selection) {
+ let object = selection[0].context.item;
+
+ return selection.length > 0 &&
+ object.type === 'table' &&
+ isBeingEdited(object);
+ },
+ view: function (selection) {
+ let component;
+ let domainObject = selection[0].context.item;
+ return {
+ show: function (element) {
+ component = TableConfigurationComponent(domainObject, openmct);
+ element.appendChild(component.$mount().$el);
+ },
+ destroy: function (element) {
+ component.$destroy();
+ element.removeChild(component.$el);
+ component = undefined;
+ }
+ }
+ },
+ priority: function () {
+ return 1;
+ }
+ }
+ }
+ return TableConfigurationViewProvider;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTable.js b/src/plugins/telemetryTable/TelemetryTable.js
new file mode 100644
index 0000000000..92dc238e88
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTable.js
@@ -0,0 +1,201 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ 'EventEmitter',
+ 'lodash',
+ './collections/BoundedTableRowCollection',
+ './collections/FilteredTableRowCollection',
+ './TelemetryTableRow',
+ './TelemetryTableConfiguration'
+], function (
+ EventEmitter,
+ _,
+ BoundedTableRowCollection,
+ FilteredTableRowCollection,
+ TelemetryTableRow,
+ TelemetryTableConfiguration
+) {
+ class TelemetryTable extends EventEmitter {
+ constructor(domainObject, rowCount, openmct) {
+ super();
+
+ this.domainObject = domainObject;
+ this.openmct = openmct;
+ this.rowCount = rowCount;
+ this.subscriptions = {};
+ this.tableComposition = undefined;
+ this.telemetryObjects = [];
+ this.outstandingRequests = 0;
+ this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
+
+ this.addTelemetryObject = this.addTelemetryObject.bind(this);
+ this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
+ this.isTelemetryObject = this.isTelemetryObject.bind(this);
+ this.refreshData = this.refreshData.bind(this);
+ this.requestDataFor = this.requestDataFor.bind(this);
+
+ this.createTableRowCollections();
+ openmct.time.on('bounds', this.refreshData);
+ }
+
+ initialize() {
+ if (this.domainObject.type === 'table') {
+ this.loadComposition();
+ } else {
+ this.addTelemetryObject(this.domainObject);
+ }
+ }
+
+ createTableRowCollections() {
+ this.boundedRows = new BoundedTableRowCollection(this.openmct);
+
+ //By default, sort by current time system, ascending.
+ this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
+ this.filteredRows.sortBy({
+ key: this.openmct.time.timeSystem().key,
+ direction: 'asc'
+ });
+ }
+
+ loadComposition() {
+ this.tableComposition = this.openmct.composition.get(this.domainObject);
+ if (this.tableComposition !== undefined){
+ this.tableComposition.load().then((composition)=>{
+ composition = composition.filter(this.isTelemetryObject);
+
+ this.configuration.addColumnsForAllObjects(composition);
+ composition.forEach(this.addTelemetryObject);
+
+ this.tableComposition.on('add', this.addTelemetryObject);
+ this.tableComposition.on('remove', this.removeTelemetryObject);
+ });
+ }
+ }
+
+ addTelemetryObject(telemetryObject) {
+ this.configuration.addColumnsForObject(telemetryObject, true);
+ this.requestDataFor(telemetryObject);
+ this.subscribeTo(telemetryObject);
+ this.telemetryObjects.push(telemetryObject);
+
+ this.emit('object-added', telemetryObject);
+ }
+
+ removeTelemetryObject(objectIdentifier) {
+ this.configuration.removeColumnsForObject(objectIdentifier, true);
+ let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
+ this.boundedRows.removeAllRowsForObject(keyString);
+ this.unsubscribe(keyString);
+ this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(objectIdentifier, object.identifier));
+
+ this.emit('object-removed', objectIdentifier);
+ }
+
+ requestDataFor(telemetryObject) {
+ this.incrementOutstandingRequests();
+
+ return this.openmct.telemetry.request(telemetryObject)
+ .then(telemetryData => {
+ let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+ let columnMap = this.getColumnMapForObject(keyString);
+ let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
+
+ let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
+ this.boundedRows.add(telemetryRows);
+ console.log('Loaded %i rows', telemetryRows.length);
+ this.decrementOutstandingRequests();
+ });
+ }
+
+ /**
+ * @private
+ */
+ incrementOutstandingRequests() {
+ if (this.outstandingRequests === 0){
+ this.emit('outstanding-requests', true);
+ }
+ this.outstandingRequests++;
+ }
+
+ /**
+ * @private
+ */
+ decrementOutstandingRequests() {
+ this.outstandingRequests--;
+
+ if (this.outstandingRequests === 0){
+ this.emit('outstanding-requests', false);
+ }
+ }
+
+ refreshData(bounds, isTick) {
+ if (!isTick) {
+ this.filteredRows.clear();
+ this.boundedRows.clear();
+ this.telemetryObjects.forEach(this.requestDataFor);
+ }
+ }
+
+ getColumnMapForObject(objectKeyString) {
+ let columns = this.configuration.getColumns();
+
+ return columns[objectKeyString].reduce((map, column) => {
+ map[column.getKey()] = column;
+ return map;
+ }, {});
+ }
+
+ subscribeTo(telemetryObject) {
+ let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+ let columnMap = this.getColumnMapForObject(keyString);
+ let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
+
+ this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
+ this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
+ });
+ }
+
+ isTelemetryObject(domainObject) {
+ return domainObject.hasOwnProperty('telemetry');
+ }
+
+ unsubscribe(keyString) {
+ this.subscriptions[keyString]();
+ delete this.subscriptions[keyString];
+ }
+
+ destroy() {
+ this.boundedRows.destroy();
+ this.filteredRows.destroy();
+ Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
+ this.openmct.time.off('bounds', this.refreshData);
+
+ if (this.tableComposition !== undefined) {
+ this.tableComposition.off('add', this.addTelemetryObject);
+ this.tableComposition.off('remove', this.removeTelemetryObject);
+ }
+ }
+ }
+
+ return TelemetryTable;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableColumn.js b/src/plugins/telemetryTable/TelemetryTableColumn.js
new file mode 100644
index 0000000000..412aca7616
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableColumn.js
@@ -0,0 +1,57 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(function () {
+ class TelemetryTableColumn {
+ constructor (openmct, metadatum) {
+ this.metadatum = metadatum;
+ this.formatter = openmct.telemetry.getValueFormatter(metadatum);
+ this.titleValue = this.metadatum.name;
+ }
+
+ getKey() {
+ return this.metadatum.key;
+ }
+
+ getTitle() {
+ return this.metadatum.name;
+ }
+
+ getMetadatum() {
+ return this.metadatum;
+ }
+
+ hasValueForDatum(telemetryDatum) {
+ return telemetryDatum.hasOwnProperty(this.metadatum.source);
+ }
+
+ getRawValue(telemetryDatum) {
+ return telemetryDatum[this.metadatum.source];
+ }
+
+ getFormattedValue(telemetryDatum) {
+ return this.formatter.format(telemetryDatum);
+ }
+
+ };
+
+ return TelemetryTableColumn;
+});
diff --git a/src/plugins/telemetryTable/TelemetryTableComponent.js b/src/plugins/telemetryTable/TelemetryTableComponent.js
new file mode 100644
index 0000000000..72ba2bfdea
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableComponent.js
@@ -0,0 +1,315 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ 'lodash',
+ 'vue',
+ './telemetry-table.html',
+ './TelemetryTable',
+ './TelemetryTableRowComponent',
+ '../../exporters/CSVExporter'
+],function (
+ _,
+ Vue,
+ TelemetryTableTemplate,
+ TelemetryTable,
+ TelemetryTableRowComponent,
+ CSVExporter
+) {
+ const VISIBLE_ROW_COUNT = 100;
+ const ROW_HEIGHT = 17;
+ const RESIZE_POLL_INTERVAL = 200;
+ const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
+
+ return function TelemetryTableComponent(domainObject, openmct) {
+ const csvExporter = new CSVExporter();
+ const table = new TelemetryTable(domainObject, VISIBLE_ROW_COUNT, openmct);
+ let processingScroll = false;
+ let updatingView = false;
+
+ return new Vue({
+ template: TelemetryTableTemplate,
+ components: {
+ 'telemetry-table-row': TelemetryTableRowComponent
+ },
+ data() {
+ return {
+ headers: {},
+ configuration: table.configuration.getConfiguration(),
+ headersCount: 0,
+ visibleRows: [],
+ columnWidths: [],
+ sizingRows: {},
+ rowHeight: ROW_HEIGHT,
+ scrollOffset: 0,
+ totalHeight: 0,
+ totalWidth: 0,
+ rowOffset: 0,
+ autoScroll: true,
+ sortOptions: {},
+ filters: {},
+ loading: false,
+ scrollable: undefined,
+ tableEl: undefined,
+ headersHolderEl: undefined,
+ calcTableWidth: '100%'
+ }
+ },
+ methods: {
+ updateVisibleRows() {
+
+ let start = 0;
+ let end = VISIBLE_ROW_COUNT;
+ let filteredRows = table.filteredRows.getRows();
+ let filteredRowsLength = filteredRows.length;
+
+ this.totalHeight = this.rowHeight * filteredRowsLength - 1;
+
+ if (filteredRowsLength < VISIBLE_ROW_COUNT) {
+ end = filteredRowsLength;
+ } else {
+ let firstVisible = this.calculateFirstVisibleRow();
+ let lastVisible = this.calculateLastVisibleRow();
+ let totalVisible = lastVisible - firstVisible;
+
+ let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
+ start = firstVisible - Math.floor(numberOffscreen / 2);
+ end = lastVisible + Math.ceil(numberOffscreen / 2);
+
+ if (start < 0) {
+ start = 0;
+ end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
+ } else if (end >= filteredRowsLength) {
+ end = filteredRowsLength;
+ start = end - VISIBLE_ROW_COUNT + 1;
+ }
+ }
+ this.rowOffset = start;
+ this.visibleRows = filteredRows.slice(start, end);
+ },
+ calculateFirstVisibleRow() {
+ return Math.floor(this.scrollable.scrollTop / this.rowHeight);
+ },
+ calculateLastVisibleRow() {
+ let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
+ return Math.floor(bottomScroll / this.rowHeight);
+ },
+ updateHeaders() {
+ let headers = table.configuration.getVisibleHeaders();
+
+ this.headers = headers;
+ this.headersCount = Object.values(headers).length;
+ Vue.nextTick().then(this.calculateColumnWidths);
+ },
+ setSizingTableWidth() {
+ let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
+
+ if (scrollW && scrollW > 0) {
+ this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
+ }
+ },
+ calculateColumnWidths() {
+ let columnWidths = [];
+ let totalWidth = 0;
+ let sizingRowEl = this.sizingTable.children[0];
+ let sizingCells = Array.from(sizingRowEl.children);
+
+ sizingCells.forEach((cell) => {
+ let columnWidth = cell.offsetWidth;
+ columnWidths.push(columnWidth + 'px');
+ totalWidth += columnWidth;
+ });
+
+ this.columnWidths = columnWidths;
+ this.totalWidth = totalWidth;
+ },
+ sortBy(columnKey) {
+ // If sorting by the same column, flip the sort direction.
+ if (this.sortOptions.key === columnKey) {
+ if (this.sortOptions.direction === 'asc') {
+ this.sortOptions.direction = 'desc';
+ } else {
+ this.sortOptions.direction = 'asc';
+ }
+ } else {
+ this.sortOptions = {
+ key: columnKey,
+ direction: 'asc'
+ }
+ }
+ table.filteredRows.sortBy(this.sortOptions);
+ },
+ scroll() {
+ if (!processingScroll) {
+ processingScroll = true;
+ requestAnimationFrame(()=> {
+ this.updateVisibleRows();
+ this.synchronizeScrollX();
+
+ if (this.shouldSnapToBottom()) {
+ this.autoScroll = true;
+ } else {
+ // If user scrolls away from bottom, disable auto-scroll.
+ // Auto-scroll will be re-enabled if user scrolls to bottom again.
+ this.autoScroll = false;
+ }
+ processingScroll = false;
+ });
+ }
+ },
+ shouldSnapToBottom() {
+ return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
+ },
+ scrollToBottom() {
+ this.scrollable.scrollTop = this.scrollable.scrollHeight;
+ },
+ synchronizeScrollX() {
+ this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
+ },
+ filterChanged(columnKey) {
+ table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
+ },
+ clearFilter(columnKey) {
+ this.filters[columnKey] = '';
+ table.filteredRows.setColumnFilter(columnKey, '');
+ },
+ rowsAdded(rows) {
+ let sizingRow;
+ if (Array.isArray(rows)) {
+ sizingRow = rows[0];
+ } else {
+ sizingRow = rows;
+ }
+ if (!this.sizingRows[sizingRow.objectKeyString]) {
+ this.sizingRows[sizingRow.objectKeyString] = sizingRow;
+ Vue.nextTick().then(this.calculateColumnWidths);
+ }
+
+ if (!updatingView) {
+ updatingView = true;
+ requestAnimationFrame(()=> {
+ this.updateVisibleRows();
+ if (this.autoScroll) {
+ Vue.nextTick().then(this.scrollToBottom);
+ }
+ updatingView = false;
+ });
+ }
+ },
+ rowsRemoved(rows) {
+ if (!updatingView) {
+ updatingView = true;
+ requestAnimationFrame(()=> {
+ this.updateVisibleRows();
+ updatingView = false;
+ });
+ }
+ },
+ exportAsCSV() {
+ const justTheData = table.filteredRows.getRows()
+ .map(row => row.getFormattedDatum());
+ const headers = Object.keys(this.headers);
+ csvExporter.export(justTheData, {
+ filename: table.domainObject.name + '.csv',
+ headers: headers
+ });
+ },
+ outstandingRequests(loading) {
+ this.loading = loading;
+ },
+ calculateTableSize() {
+ this.setSizingTableWidth();
+ Vue.nextTick().then(this.calculateColumnWidths);
+ },
+ pollForResize() {
+ let el = this.$el;
+ let width = el.clientWidth;
+ let height = el.clientHeight;
+
+ this.resizePollHandle = setInterval(() => {
+ if (el.clientWidth !== width || el.clientHeight !== height) {
+ this.calculateTableSize();
+ width = el.clientWidth;
+ height = el.clientHeight;
+ }
+ }, RESIZE_POLL_INTERVAL);
+ },
+ updateConfiguration(configuration) {
+ this.configuration = configuration;
+ this.updateHeaders();
+ },
+ addObject() {
+ this.updateHeaders();
+ },
+ removeObject(objectIdentifier) {
+ let objectKeyString = openmct.objects.makeKeyString(objectIdentifier);
+ delete this.sizingRows[objectKeyString];
+ this.updateHeaders();
+ }
+ },
+ created() {
+ this.filterChanged = _.debounce(this.filterChanged, 500);
+ },
+ mounted() {
+ table.on('object-added', this.addObject);
+ table.on('object-removed', this.removeObject);
+ table.on('outstanding-requests', this.outstandingRequests);
+
+ table.filteredRows.on('add', this.rowsAdded);
+ table.filteredRows.on('remove', this.rowsRemoved);
+ table.filteredRows.on('sort', this.updateVisibleRows);
+ table.filteredRows.on('filter', this.updateVisibleRows);
+
+ //Default sort
+ this.sortOptions = table.filteredRows.sortBy();
+ this.scrollable = this.$el.querySelector('.t-scrolling');
+ this.sizingTable = this.$el.querySelector('.js-sizing-table');
+ this.headersHolderEl = this.$el.querySelector('.mct-table-headers-w');
+
+ table.configuration.on('change', this.updateConfiguration);
+
+ this.calculateTableSize();
+ this.pollForResize();
+
+ table.initialize();
+ },
+ destroyed() {
+ table.off('object-added', this.addObject);
+ table.off('object-removed', this.removeObject);
+ table.off('outstanding-requests', this.outstandingRequests);
+
+ table.filteredRows.off('add', this.rowsAdded);
+ table.filteredRows.off('remove', this.rowsRemoved);
+ table.filteredRows.off('sort', this.updateVisibleRows);
+ table.filteredRows.off('filter', this.updateVisibleRows);
+
+ table.configuration.off('change', this.updateConfiguration);
+
+ clearInterval(this.resizePollHandle);
+
+ table.configuration.destroy();
+
+ table.destroy();
+ }
+ });
+ }
+ });
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableConfiguration.js b/src/plugins/telemetryTable/TelemetryTableConfiguration.js
new file mode 100644
index 0000000000..e29a9715c9
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableConfiguration.js
@@ -0,0 +1,141 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ 'lodash',
+ 'EventEmitter',
+ './TelemetryTableColumn',
+], function (_, EventEmitter, TelemetryTableColumn) {
+
+ class TelemetryTableConfiguration extends EventEmitter{
+ constructor(domainObject, openmct) {
+ super();
+
+ this.domainObject = domainObject;
+ this.openmct = openmct;
+ this.columns = {};
+
+ this.addColumnsForObject = this.addColumnsForObject.bind(this);
+ this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
+ this.objectMutated = this.objectMutated.bind(this);
+
+ this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
+ }
+
+ getConfiguration() {
+ let configuration = this.domainObject.configuration || {};
+ configuration.hiddenColumns = configuration.hiddenColumns || {};
+ return configuration;
+ }
+
+ updateConfiguration(configuration) {
+ this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
+ }
+
+ /**
+ * @private
+ * @param {*} object
+ */
+ objectMutated(object) {
+ let oldConfiguration = this.domainObject.configuration;
+
+ //Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
+ this.domainObject = JSON.parse(JSON.stringify(object));
+ if (!_.eq(object.configuration, oldConfiguration)){
+ this.emit('change', object.configuration);
+ }
+ }
+
+ addColumnsForAllObjects(objects) {
+ objects.forEach(object => this.addColumnsForObject(object, false));
+ }
+
+ addColumnsForObject(telemetryObject) {
+ let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
+ let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
+ this.columns[objectKeyString] = [];
+
+ metadataValues.forEach(metadatum => {
+ let column = new TelemetryTableColumn(this.openmct, metadatum);
+ this.columns[objectKeyString].push(column);
+ });
+ }
+
+ removeColumnsForObject(objectIdentifier) {
+ let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
+ let columnsToRemove = this.columns[objectKeyString];
+
+ delete this.columns[objectKeyString];
+ columnsToRemove.forEach((column) => {
+ //There may be more than one column with the same key (eg. time system columns)
+ if (!this.hasColumnWithKey(column.getKey())) {
+ let configuration = this.domainObject.configuration;
+ delete configuration.hiddenColumns[column.getKey()];
+ // If there are no more columns with this key, delete any configuration, and trigger
+ // a column refresh.
+ this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
+ }
+ });
+ }
+
+ hasColumnWithKey(columnKey) {
+ return _.flatten(Object.values(this.columns))
+ .findIndex(column => column.getKey() === columnKey) !== -1;
+ }
+
+ getColumns() {
+ return this.columns;
+ }
+
+ getAllHeaders() {
+ let flattenedColumns = _.flatten(Object.values(this.columns));
+ let headers = _.uniq(flattenedColumns, false, column => column.getKey())
+ .reduce(fromColumnsToHeadersMap, {});
+
+ function fromColumnsToHeadersMap(headersMap, column){
+ headersMap[column.getKey()] = column.getTitle();
+ return headersMap;
+ }
+
+ return headers;
+ }
+
+ getVisibleHeaders() {
+ let headers = this.getAllHeaders();
+ let configuration = this.getConfiguration();
+
+ Object.keys(headers).forEach((headerKey) => {
+ if (configuration.hiddenColumns[headerKey] === true) {
+ delete headers[headerKey];
+ }
+ });
+
+ return headers;
+ }
+
+ destroy() {
+ this.unlistenFromMutation();
+ }
+ }
+
+ return TelemetryTableConfiguration;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableRow.js b/src/plugins/telemetryTable/TelemetryTableRow.js
new file mode 100644
index 0000000000..804a4ef44d
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableRow.js
@@ -0,0 +1,82 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([], function () {
+ class TelemetryTableRow {
+ constructor(datum, columns, objectKeyString, limitEvaluator) {
+ this.columns = columns;
+
+ this.datum = createNormalizedDatum(datum, columns);
+ this.limitEvaluator = limitEvaluator;
+ this.objectKeyString = objectKeyString;
+ }
+
+ getFormattedDatum() {
+ return Object.values(this.columns)
+ .reduce((formattedDatum, column) => {
+ formattedDatum[column.getKey()] = this.getFormattedValue(column.getKey());
+ return formattedDatum;
+ }, {});
+ }
+
+ getFormattedValue(key) {
+ let column = this.columns[key];
+ return column.getFormattedValue(this.datum[key]);
+ }
+
+ getRowLimitClass() {
+ if (!this.rowLimitClass) {
+ let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
+ this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass;
+ }
+ return this.rowLimitClass;
+ }
+
+ getCellLimitClasses() {
+ if (!this.cellLimitClasses) {
+ this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
+ let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
+ alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
+
+ return alarmStateMap;
+ }, {});
+ }
+ return this.cellLimitClasses;
+ }
+ }
+
+ /**
+ * Normalize the structure of datums to assist sorting and merging of columns.
+ * Maps all sources to keys.
+ * @private
+ * @param {*} telemetryDatum
+ * @param {*} metadataValues
+ */
+ function createNormalizedDatum(datum, columns) {
+ return Object.values(columns).reduce((normalizedDatum, column) => {
+ normalizedDatum[column.getKey()] = column.getRawValue(datum);
+ return normalizedDatum;
+ }, {});
+ }
+
+ return TelemetryTableRow;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableRowComponent.js b/src/plugins/telemetryTable/TelemetryTableRowComponent.js
new file mode 100644
index 0000000000..6fdac03979
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableRowComponent.js
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ './telemetry-table-row.html',
+],function (
+ TelemetryTableRowTemplate
+) {
+ return {
+ template: TelemetryTableRowTemplate,
+ data: function () {
+ return {
+ rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
+ formattedRow: this.row.getFormattedDatum(),
+ rowLimitClass: this.row.getRowLimitClass(),
+ cellLimitClasses: this.row.getCellLimitClasses()
+ }
+ },
+ props: {
+ headers: {
+ type: Object,
+ required: true
+ },
+ row: {
+ type: Object,
+ required: true
+ },
+ columnWidths: {
+ type: Array,
+ required: false,
+ default: [],
+ },
+ rowIndex: {
+ type: Number,
+ required: false,
+ default: undefined
+ },
+ rowOffset: {
+ type: Number,
+ required: false,
+ default: 0
+ },
+ rowHeight: {
+ type: Number,
+ required: false,
+ default: 0
+ },
+ configuration: {
+ type: Object,
+ required: true
+ }
+ },
+ methods: {
+ calculateRowTop: function (rowOffset) {
+ this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
+ },
+ formatRow: function (row) {
+ this.formattedRow = row.getFormattedDatum();
+ this.rowLimitClass = row.getRowLimitClass();
+ this.cellLimitClasses = row.getCellLimitClasses();
+ }
+ },
+ watch: {
+ rowOffset: 'calculateRowTop',
+ row: {
+ handler: 'formatRow',
+ deep: false
+ }
+ }
+ };
+ });
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableType.js b/src/plugins/telemetryTable/TelemetryTableType.js
new file mode 100644
index 0000000000..395a81fe68
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableType.js
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(function () {
+ function TelemetryTableType() {
+ return {
+ name: 'Telemetry Table',
+ description: 'Display telemetry values for the current time bounds in tabular form. Supports filtering and sorting.',
+ creatable: true,
+ cssClass: 'icon-tabular-realtime',
+ initialize(domainObject) {
+ domainObject.composition = [];
+ domainObject.configuration = {
+ columns: {}
+ };
+ }
+ }
+ }
+ return TelemetryTableType;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/TelemetryTableViewProvider.js b/src/plugins/telemetryTable/TelemetryTableViewProvider.js
new file mode 100644
index 0000000000..c162ce4d90
--- /dev/null
+++ b/src/plugins/telemetryTable/TelemetryTableViewProvider.js
@@ -0,0 +1,52 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
+ function TelemetryTableViewProvider(openmct) {
+ return {
+ key: 'table',
+ name: 'Telemetry Table',
+ editable: true,
+ canView: function (domainObject) {
+ return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
+ },
+ view: function (domainObject) {
+ let component;
+ return {
+ show: function (element) {
+ component = new TelemetryTableComponent(domainObject, openmct);
+ element.appendChild(component.$mount().$el);
+ },
+ destroy: function (element) {
+ component.$destroy();
+ element.removeChild(component.$el);
+ component = undefined;
+ }
+ }
+ },
+ priority: function () {
+ return 1;
+ }
+ }
+ }
+ return TelemetryTableViewProvider;
+});
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/collections/BoundedTableRowCollection.js b/src/plugins/telemetryTable/collections/BoundedTableRowCollection.js
new file mode 100644
index 0000000000..1157a3ef49
--- /dev/null
+++ b/src/plugins/telemetryTable/collections/BoundedTableRowCollection.js
@@ -0,0 +1,139 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(
+ [
+ 'lodash',
+ './SortedTableRowCollection'
+ ],
+ function (
+ _,
+ SortedTableRowCollection
+ ) {
+
+ class BoundedTableRowCollection extends SortedTableRowCollection {
+ constructor (openmct) {
+ super();
+
+ this.futureBuffer = new SortedTableRowCollection();
+ this.openmct = openmct;
+
+ this.sortByTimeSystem = this.sortByTimeSystem.bind(this)
+ this.bounds = this.bounds.bind(this)
+
+ this.sortByTimeSystem(openmct.time.timeSystem());
+ openmct.time.on('timeSystem', this.sortByTimeSystem);
+
+ this.lastBounds = openmct.time.bounds();
+ openmct.time.on('bounds', this.bounds);
+ }
+
+ addOne (item) {
+ // Insert into either in-bounds array, or the future buffer.
+ // Data in the future buffer will be re-evaluated for possible
+ // insertion on next bounds change
+ let beforeStartOfBounds = item.datum[this.sortOptions.key] < this.lastBounds.start;
+ let afterEndOfBounds = item.datum[this.sortOptions.key] > this.lastBounds.end;
+
+ if (!afterEndOfBounds && !beforeStartOfBounds) {
+ return super.addOne(item);
+ } else if (afterEndOfBounds) {
+ this.futureBuffer.addOne(item);
+ }
+ return false;
+ }
+
+ sortByTimeSystem(timeSystem) {
+ this.sortBy({key: timeSystem.key, direction: 'asc'});
+ this.futureBuffer.sortBy({key: timeSystem.key, direction: 'asc'});
+ }
+
+ /**
+ * This function is optimized for ticking - it assumes that start and end
+ * bounds will only increase and as such this cannot be used for decreasing
+ * bounds changes.
+ *
+ * An implication of this is that data will not be discarded that exceeds
+ * the given end bounds. For arbitrary bounds changes, it's assumed that
+ * a telemetry requery is performed anyway, and the collection is cleared
+ * and repopulated.
+ *
+ * @fires TelemetryCollection#added
+ * @fires TelemetryCollection#discarded
+ * @param bounds
+ */
+ bounds (bounds) {
+ let startChanged = this.lastBounds.start !== bounds.start;
+ let endChanged = this.lastBounds.end !== bounds.end;
+
+ let startIndex = 0;
+ let endIndex = 0;
+
+ let discarded = [];
+ let added = [];
+ let testValue = {
+ datum: {}
+ };
+
+ this.lastBounds = bounds;
+
+ if (startChanged) {
+ testValue.datum[this.sortOptions.key] = bounds.start;
+ // Calculate the new index of the first item within the bounds
+ startIndex = this.sortedIndex(this.rows, testValue);
+ discarded = this.rows.splice(0, startIndex);
+ }
+
+ if (endChanged) {
+ testValue.datum[this.sortOptions.key] = bounds.end;
+ // Calculate the new index of the last item in bounds
+ endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
+ added = this.futureBuffer.rows.splice(0, endIndex);
+ added.forEach((datum) => this.rows.push(datum));
+ }
+
+ if (discarded && discarded.length > 0) {
+ /**
+ * 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
+ */
+ this.emit('remove', discarded);
+ }
+ if (added && added.length > 0) {
+ /**
+ * 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
+ */
+ this.emit('add', added);
+ }
+ }
+
+ destroy() {
+ this.openmct.time.off('timeSystem', this.sortByTimeSystem);
+ this.openmct.time.off('bounds', this.bounds);
+ }
+ }
+ return BoundedTableRowCollection;
+});
diff --git a/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js b/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js
new file mode 100644
index 0000000000..14f866fb49
--- /dev/null
+++ b/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js
@@ -0,0 +1,112 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(
+ [
+ './SortedTableRowCollection'
+ ],
+ function (
+ SortedTableRowCollection
+ ) {
+ class FilteredTableRowCollection extends SortedTableRowCollection {
+ constructor(masterCollection) {
+ super();
+
+ this.masterCollection = masterCollection;
+ this.columnFilters = {};
+
+ //Synchronize with master collection
+ this.masterCollection.on('add', this.add);
+ this.masterCollection.on('remove', this.remove);
+
+ //Default to master collection's sort options
+ this.sortOptions = masterCollection.sortBy();
+ }
+
+ setColumnFilter(columnKey, filter) {
+ filter = filter.trim().toLowerCase();
+
+ let rowsToFilter = this.getRowsToFilter(columnKey, filter);
+ if (filter.length === 0) {
+ delete this.columnFilters[columnKey];
+ } else {
+ this.columnFilters[columnKey] = filter;
+ }
+ this.rows = rowsToFilter.filter(this.matchesFilters, this);
+ this.emit('filter');
+ }
+
+ /**
+ * @private
+ */
+ getRowsToFilter(columnKey, filter) {
+ if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
+ return this.getRows();
+ } else {
+ return this.masterCollection.getRows();
+ }
+ }
+
+ /**
+ * @private
+ */
+ isSubsetOfCurrentFilter(columnKey, filter) {
+ return this.columnFilters[columnKey] &&
+ filter.startsWith(this.columnFilters[columnKey]) &&
+ // startsWith check will otherwise fail when filter cleared
+ // because anyString.startsWith('') === true
+ filter !== '';
+ }
+
+ addOne(row) {
+ return this.matchesFilters(row) && super.addOne(row);
+ }
+
+ /**
+ * @private
+ */
+ matchesFilters(row) {
+ let doesMatchFilters = true;
+ for (const key in this.columnFilters) {
+ if (!this.rowHasColumn(row, key)) {
+ return false;
+ } else {
+ let formattedValue = row.getFormattedValue(key).toLowerCase();
+ doesMatchFilters = doesMatchFilters &&
+ formattedValue.indexOf(this.columnFilters[key]) !== -1;
+ }
+ }
+ return doesMatchFilters;
+ }
+
+ rowHasColumn(row, key) {
+ return row.columns.hasOwnProperty(key);
+ }
+
+ destroy() {
+ this.masterCollection.off('add', this.add);
+ this.masterCollection.off('remove', this.remove);
+ }
+ }
+
+ return FilteredTableRowCollection;
+ });
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/collections/SortedTableRowCollection.js b/src/plugins/telemetryTable/collections/SortedTableRowCollection.js
new file mode 100644
index 0000000000..756896f25f
--- /dev/null
+++ b/src/plugins/telemetryTable/collections/SortedTableRowCollection.js
@@ -0,0 +1,218 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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(
+ [
+ 'lodash',
+ 'EventEmitter'
+ ],
+ function (
+ _,
+ EventEmitter
+ ) {
+ const LESS_THAN = -1;
+ const EQUAL = 0;
+ const GREATER_THAN = 1;
+
+ /**
+ * @constructor
+ */
+ class SortedTableRowCollection extends EventEmitter {
+ constructor () {
+ super();
+
+ this.dupeCheck = false;
+ this.rows = [];
+
+ this.add = this.add.bind(this);
+ this.remove = this.remove.bind(this);
+ }
+
+ /**
+ * Add a datum or array of data to this telemetry collection
+ * @fires TelemetryCollection#added
+ * @param {object | object[]} rows
+ */
+ add(rows) {
+ if (Array.isArray(rows)) {
+ this.dupeCheck = false;
+
+ let rowsAdded = rows.filter(this.addOne, this);
+ if (rowsAdded.length > 0) {
+ this.emit('add', rowsAdded);
+ }
+ this.dupeCheck = true;
+ } else {
+ let wasAdded = this.addOne(rows);
+ if (wasAdded) {
+ this.emit('add', rows);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ addOne(row) {
+ if (this.sortOptions === undefined) {
+ throw 'Please specify sort options';
+ }
+
+ let isDuplicate = false;
+
+ // Going to check for duplicates. Bound the search problem to
+ // items around the given time. Use sortedIndex because it
+ // employs a binary search which is O(log n). Can use binary search
+ // based on time stamp because the array is guaranteed ordered due
+ // to sorted insertion.
+ let startIx = this.sortedIndex(this.rows, row);
+ let endIx = undefined;
+
+ if (this.dupeCheck && startIx !== this.rows.length) {
+ endIx = this.sortedLastIndex(this.rows, row);
+
+ // Create an array of potential dupes, based on having the
+ // same time stamp
+ let potentialDupes = this.rows.slice(startIx, endIx + 1);
+ // Search potential dupes for exact dupe
+ isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, row)) > -1;
+ }
+
+ if (!isDuplicate) {
+ this.rows.splice(endIx || startIx, 0, row);
+ return true;
+ }
+ return false;
+ }
+
+ sortedLastIndex(rows, testRow) {
+ return this.sortedIndex(rows, testRow, _.sortedLastIndex);
+ }
+ /**
+ * Finds the correct insertion point for the given row.
+ * Leverages lodash's `sortedIndex` function which implements a binary search.
+ * @private
+ */
+ sortedIndex(rows, testRow, lodashFunction) {
+ const sortOptionsKey = this.sortOptions.key;
+ lodashFunction = lodashFunction || _.sortedIndex;
+
+ if (this.sortOptions.direction === 'asc') {
+ return lodashFunction(rows, testRow, (thisRow) => {
+ return thisRow.datum[sortOptionsKey];
+ });
+ } else {
+ const testRowValue = testRow.datum[this.sortOptions.key];
+ // Use a custom comparison function to support descending sort.
+ return lodashFunction(rows, testRow, (thisRow) => {
+ const thisRowValue = thisRow.datum[sortOptionsKey];
+ if (testRowValue === thisRowValue) {
+ return EQUAL;
+ } else if (testRowValue < thisRowValue) {
+ return LESS_THAN;
+ } else {
+ return GREATER_THAN;
+ }
+ });
+ }
+ }
+
+ /**
+ * Sorts the telemetry collection based on the provided sort field
+ * 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();
+ * let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
+ * return {
+ * // define an object property to demonstrate nested paths
+ * timestamp: {
+ * ms: now - value * 1000,
+ * text:
+ * },
+ * value: value
+ * }
+ * });
+ * let collection = new TelemetryCollection();
+ *
+ * collection.add(telemetry);
+ *
+ * // Sort by telemetry value
+ * collection.sortBy({
+ * key: 'value', direction: 'asc'
+ * });
+ *
+ * // Sort by ms since epoch
+ * collection.sort({
+ * key: 'timestamp.ms',
+ * direction: 'asc'
+ * });
+ *
+ * // Sort by 'text' attribute, descending
+ * collection.sort("timestamp.text");
+ *
+ *
+ * @param {object} sortOptions An object specifying a sort key, and direction.
+ */
+ sortBy(sortOptions) {
+ if (arguments.length > 0) {
+ this.sortOptions = sortOptions;
+ this.rows = _.sortByOrder(this.rows, 'datum.' + sortOptions.key, sortOptions.direction);
+ this.emit('sort');
+ }
+ // Return duplicate to avoid direct modification of underlying object
+ return Object.assign({}, this.sortOptions);
+ }
+
+ removeAllRowsForObject(objectKeyString) {
+ let removed = [];
+ this.rows = this.rows.filter(row => {
+ if (row.objectKeyString === objectKeyString) {
+ removed.push(row);
+ return false;
+ }
+ return true;
+ });
+ this.emit('remove', removed);
+ }
+
+ remove(removedRows) {
+ this.rows = this.rows.filter(row => {
+ return removedRows.indexOf(row) === -1;
+ });
+ this.emit('remove', removedRows);
+ }
+
+ getRows () {
+ return this.rows;
+ }
+
+ clear() {
+ let removedRows = this.rows;
+ this.rows = [];
+ this.emit('remove', removedRows);
+ }
+ }
+ return SortedTableRowCollection;
+});
diff --git a/src/plugins/telemetryTable/plugin.js b/src/plugins/telemetryTable/plugin.js
new file mode 100644
index 0000000000..1cc0750ef0
--- /dev/null
+++ b/src/plugins/telemetryTable/plugin.js
@@ -0,0 +1,39 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2018, 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([
+ './TelemetryTableViewProvider',
+ './TableConfigurationViewProvider',
+ './TelemetryTableType'
+ ], function (
+ TelemetryTableViewProvider,
+ TableConfigurationViewProvider,
+ TelemetryTableType
+ ) {
+ return function plugin() {
+ return function install(openmct) {
+ openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct));
+ openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
+ openmct.types.addType('table', TelemetryTableType());
+ };
+ };
+ });
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/table-configuration.html b/src/plugins/telemetryTable/table-configuration.html
new file mode 100644
index 0000000000..b21e1e7fe1
--- /dev/null
+++ b/src/plugins/telemetryTable/table-configuration.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/telemetry-table-row.html b/src/plugins/telemetryTable/telemetry-table-row.html
new file mode 100644
index 0000000000..e77872e640
--- /dev/null
+++ b/src/plugins/telemetryTable/telemetry-table-row.html
@@ -0,0 +1,6 @@
+
+ {{formattedRow[key]}} |
+
\ No newline at end of file
diff --git a/src/plugins/telemetryTable/telemetry-table.html b/src/plugins/telemetryTable/telemetry-table.html
new file mode 100644
index 0000000000..78fa917a2c
--- /dev/null
+++ b/src/plugins/telemetryTable/telemetry-table.html
@@ -0,0 +1,64 @@
+
\ No newline at end of file
diff --git a/src/styles/_table.scss b/src/styles/_table.scss
index 534da1c432..7b2deccdc0 100644
--- a/src/styles/_table.scss
+++ b/src/styles/_table.scss
@@ -20,47 +20,52 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
-mct-table {
- .mct-sizing-table {
- z-index: -1;
- visibility: hidden;
- position: absolute !important;
+.mct-sizing-table {
+ z-index: -1;
+ visibility: hidden;
+ position: absolute !important;
+
+ //Add some padding to allow for decorations such as limits indicator
+ td {
+ padding-right: 15px;
+ padding-left: 10px;
+ white-space: nowrap;
+ }
+}
+
+.mct-table {
+ tr {
+ display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
+ align-items: stretch;
+ }
+
+ td, th {
+ box-sizing: border-box;
+ display: block;
+ flex: 1 0 auto;
+ white-space: nowrap;
+ }
+
+ thead {
+ display: block;
+ }
+
+ tbody {
+ tr {
+ position: absolute;
+ height: 18px; // Needed when a row has empty values in its cells
+ }
- //Add some padding to allow for decorations such as limits indicator
td {
- padding-right: 15px;
- padding-left: 10px;
- white-space: nowrap;
- }
- }
-
- .mct-table {
- thead {
- display: block;
- tr {
- display: block;
- white-space: nowrap;
- th {
- display: inline-block;
- box-sizing: border-box;
- }
- }
- }
- tbody {
- tr {
- position: absolute;
- white-space: nowrap;
- display: block;
- }
- td {
- white-space: nowrap;
- overflow: hidden;
- box-sizing: border-box;
- display: inline-block;
- }
+ overflow: hidden;
+ box-sizing: border-box;
+ display: inline-block;
+ text-overflow: ellipsis;
}
}
+}
+.l-telemetry-table {
.l-control-bar {
margin-bottom: 3px;
}