Merge remote-tracking branch 'origin/open670'

This commit is contained in:
Pete Richards 2016-03-03 11:28:28 -08:00
commit 7e60593501
20 changed files with 2151 additions and 0 deletions

View File

@ -77,6 +77,7 @@ define([
'./platform/features/plot/bundle', './platform/features/plot/bundle',
'./platform/features/scrolling/bundle', './platform/features/scrolling/bundle',
'./platform/features/timeline/bundle', './platform/features/timeline/bundle',
'./platform/features/table/bundle',
'./platform/forms/bundle', './platform/forms/bundle',
'./platform/identity/bundle', './platform/identity/bundle',
'./platform/persistence/aggregator/bundle', './platform/persistence/aggregator/bundle',

View File

@ -0,0 +1,125 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define*/
define([
"./src/directives/MCTTable",
"./src/controllers/TelemetryTableController",
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
'../../commonUI/browse/src/InspectorRegion',
"legacyRegistry"
], function (
MCTTable,
TelemetryTableController,
TableOptionsController,
Region,
InspectorRegion,
legacyRegistry
) {
"use strict";
/**
* 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": "Table",
"glyph": "\ue605",
"description": "A table for displaying telemetry data",
"features": "creation",
"delegates": [
"telemetry"
],
"inspector": tableInspector,
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"views": [
"table"
]
}
],
"controllers": [
{
"key": "TelemetryTableController",
"implementation": TelemetryTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "TableOptionsController",
"implementation": TableOptionsController,
"depends": ["$scope"]
}
],
"views": [
{
"name": "Table",
"key": "table",
"glyph": "\ue605",
"templateUrl": "templates/table.html",
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
}
],
"directives": [
{
"key": "mctTable",
"implementation": MCTTable,
"depends": ["$timeout"]
}
],
"representations": [
{
"key": "table-options-edit",
"templateUrl": "templates/table-options-edit.html"
}
]
}
});
});

View File

@ -0,0 +1,64 @@
<div class="l-view-section scrolling"
style="overflow: auto;"
>
<table class="filterable"
ng-style="overrideRowPositioning && {
height: totalHeight + 'px',
'table-layout': overrideRowPositioning ? 'fixed' : 'auto'
}">
<thead>
<tr>
<th ng-repeat="header in displayHeaders"
ng-style="overrideRowPositioning && {
width: columnWidths[$index] + 'px',
'max-width': columnWidths[$index] + 'px',
overflow: 'none',
'box-sizing': 'border-box'
}"
ng-class="[
enableSort ? 'sortable' : '',
sortColumn === header ? 'sort' : '',
sortDirection || ''
].join(' ')"
ng-click="toggleSort(header)">
{{ header }}
</th>
</tr>
<tr ng-if="enableFilter" class="s-filters">
<th ng-repeat="header in displayHeaders"
ng-style="overrideRowPositioning && {
width: columnWidths[$index] + 'px',
'max-width': columnWidths[$index] + 'px',
overflow: 'none',
'box-sizing': 'border-box'
}">
<input type="text"
ng-model="filters[header]"/>
</th>
</tr>
</thead>
<tbody ng-style="overrideRowPositioning ? '' : {
'opacity': '0.0'
}">
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
ng-style="overrideRowPositioning && {
position: 'absolute',
top: visibleRow.offsetY + 'px',
}">
<td ng-repeat="header in displayHeaders"
ng-style="overrideRowPositioning && {
width: columnWidths[$index] + 'px',
'white-space': 'nowrap',
'max-width': columnWidths[$index] + 'px',
overflow: 'hidden',
'box-sizing': 'border-box'
}"
class="{{visibleRow.contents[header].cssClass}}">
{{ visibleRow.contents[header].text }}
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,30 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div ng-controller="TableOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<mct-form
ng-model="configuration.table.columns"
structure="columnsForm"
name="columnsFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
</mct-form>
</div>

View File

@ -0,0 +1,8 @@
<div ng-controller="TelemetryTableController">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true">
</mct-table>
</div>

View File

@ -0,0 +1,64 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,moment*/
/**
* Module defining DomainColumn.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/table
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(domainMetadata, telemetryFormatter) {
this.domainMetadata = domainMetadata;
this.telemetryFormatter = telemetryFormatter;
}
DomainColumn.prototype.getTitle = function () {
return this.domainMetadata.name;
};
DomainColumn.prototype.getValue = function (domainObject, datum) {
return {
text: this.telemetryFormatter.formatDomainValue(
datum[this.domainMetadata.key],
this.domainMetadata.format
)
};
};
return DomainColumn;
}
);

View File

@ -0,0 +1,54 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,Promise*/
/**
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report the name of the domain object
* which exposed specific telemetry values.
*
* @memberof platform/features/table
* @constructor
*/
function NameColumn() {
}
NameColumn.prototype.getTitle = function () {
return "Name";
};
NameColumn.prototype.getValue = function (domainObject) {
return {
text: domainObject.getModel().name
};
};
return NameColumn;
}
);

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,Promise*/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
"use strict";
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the ScrollingListController.
*
* @memberof platform/features/table
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn(rangeMetadata, telemetryFormatter) {
this.rangeMetadata = rangeMetadata;
this.telemetryFormatter = telemetryFormatter;
}
RangeColumn.prototype.getTitle = function () {
return this.rangeMetadata.name;
};
RangeColumn.prototype.getValue = function (domainObject, datum) {
var range = this.rangeMetadata.key,
limit = domainObject.getCapability('limit'),
value = isNaN(datum[range]) ? datum[range] : parseFloat(datum[range]),
alarm = limit && limit.evaluate(datum, range);
return {
cssClass: alarm && alarm.cssClass,
text: typeof(value) === 'undefined' ? undefined : this.telemetryFormatter.formatRangeValue(value)
};
};
return RangeColumn;
}
);

View File

@ -0,0 +1,180 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,moment*/
define(
[
'./DomainColumn',
'./RangeColumn',
'./NameColumn'
],
function (DomainColumn, RangeColumn, NameColumn) {
"use strict";
/**
* Class that manages table metadata, state, and contents.
* @memberof platform/features/table
* @param domainObject
* @constructor
*/
function TableConfiguration(domainObject, telemetryFormatter) {
this.domainObject = domainObject;
this.columns = [];
this.telemetryFormatter = telemetryFormatter;
}
/**
* Build column definitions based on supplied telemetry metadata
* @param metadata Metadata describing the domains and ranges available
* @returns {TableConfiguration} This object
*/
TableConfiguration.prototype.buildColumns = function(metadata) {
var self = this;
this.columns = [];
if (metadata) {
if (metadata.length > 1){
self.addColumn(new NameColumn(), 0);
}
metadata.forEach(function (metadatum) {
//Push domains first
(metadatum.domains || []).forEach(function (domainMetadata) {
self.addColumn(new DomainColumn(domainMetadata, self.telemetryFormatter));
});
(metadatum.ranges || []).forEach(function (rangeMetadata) {
self.addColumn(new RangeColumn(rangeMetadata, self.telemetryFormatter));
});
});
}
return this;
};
/**
* Add a column definition to this Table
* @param {RangeColumn | DomainColumn | NameColumn} column
* @param {Number} [index] Where the column should appear (will be
* affected by column filtering)
*/
TableConfiguration.prototype.addColumn = function (column, index) {
if (typeof index === 'undefined') {
this.columns.push(column);
} else {
this.columns.splice(index, 0, column);
}
};
/**
* @private
* @param column
* @returns {*|string}
*/
TableConfiguration.prototype.getColumnTitle = function (column) {
return column.getTitle();
};
/**
* Get a simple list of column titles
* @returns {Array} The titles of the columns
*/
TableConfiguration.prototype.getHeaders = function() {
var self = this;
return this.columns.map(function (column, i){
return self.getColumnTitle(column) || 'Column ' + (i + 1);
});
};
/**
* 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, datum) {
var self = this;
return this.columns.reduce(function(rowObject, column, i){
var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1),
columnValue = column.getValue(telemetryObject, datum);
if (columnValue !== undefined && columnValue.text === undefined){
columnValue.text = '';
}
// Don't replace something with nothing.
// This occurs when there are multiple columns with the
// column title
if (rowObject[columnTitle] === undefined || 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;
});
};
/**
* 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.getColumnConfiguration = 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.getHeaders().forEach(function(columnTitle) {
configuration[columnTitle] = typeof defaultConfig[columnTitle] === 'undefined' ? true : defaultConfig[columnTitle];
});
return configuration;
};
return TableConfiguration;
}
);

View File

@ -0,0 +1,354 @@
/*global define*/
define(
[],
function () {
"use strict";
function MCTTableController($scope, $timeout, element) {
var self = this;
this.$scope = $scope;
this.element = element;
this.$timeout = $timeout;
this.maxDisplayRows = 50;
$scope.visibleRows = [];
$scope.overrideRowPositioning = false;
/**
* 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;
}
}
setDefaults($scope);
element.find('div').on('scroll', this.onScroll.bind(this));
$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;
}
self.updateRows($scope.rows);
};
$scope.$watchCollection('filters', function () {
self.updateRows(self.$scope.rows);
});
$scope.$watchCollection('headers', this.updateHeaders.bind(this));
$scope.$watchCollection('rows', this.updateRows.bind(this));
}
/**
* On scroll, calculate which rows indexes are visible and
* ensure that an equal number of rows are preloaded for
* scrolling in either direction.
*/
MCTTableController.prototype.onScroll = function (event) {
var self = this,
topScroll = event.target.scrollTop,
bottomScroll = topScroll + event.target.offsetHeight,
firstVisible,
lastVisible,
totalVisible,
numberOffscreen,
start,
end;
if (this.$scope.displayRows.length < this.maxDisplayRows) {
return;
}
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) / this.$scope.rowHeight
);
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) / this.$scope.rowHeight
);
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 = this.$scope.visibleRows.length - 1;
} else if (end >= this.$scope.displayRows.length) {
end = this.$scope.displayRows.length - 1;
start = end - this.maxDisplayRows + 1;
}
if (this.$scope.visibleRows[0].rowIndex === start &&
this.$scope.visibleRows[this.$scope.visibleRows.length-1]
.rowIndex === end) {
return; // don't update if no changes are required.
}
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
.map(function(row, i) {
return {
rowIndex: start + i,
offsetY: ((start + i) * self.$scope.rowHeight) +
self.$scope.headerHeight,
contents: row
};
});
this.$scope.$digest();
};
/**
* Update table headers with new headers. If filtering is
* enabled, reset filters. If sorting is enabled, reset
* sorting.
*/
MCTTableController.prototype.updateHeaders = 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 current sorted on.
if (this.$scope.enableSort && newHeaders.indexOf(this.$scope.sortColumn) === -1) {
this.$scope.sortColumn = undefined;
this.$scope.sortDirection = undefined;
}
this.updateRows(this.$scope.rows);
};
/**
* Read styles from the DOM and use them to calculate offsets
* for individual rows.
*/
MCTTableController.prototype.setElementSizes = function () {
var self = this,
thead = this.element.find('thead'),
tbody = this.element.find('tbody'),
firstRow = tbody.find('tr'),
column = firstRow.find('td'),
headerHeight = thead.prop('offsetHeight'),
//row height is hard-coded for now.
rowHeight = 20,
overallHeight = headerHeight + (rowHeight * (this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
this.$scope.columnWidths = [];
while (column.length) {
this.$scope.columnWidths.push(column.prop('offsetWidth'));
column = column.next();
}
this.$scope.headerHeight = headerHeight;
this.$scope.rowHeight = rowHeight;
this.$scope.totalHeight = overallHeight;
this.$scope.visibleRows = this.$scope.displayRows.slice(0, this.maxDisplayRows).map(function(row, i) {
return {
rowIndex: i,
offsetY: (i * self.$scope.rowHeight) + self.$scope.headerHeight,
contents: row
};
});
this.$scope.overrideRowPositioning = true;
};
/**
* 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) {
/**
* 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.
*/
function genericComparator(a, b) {
if (typeof a === "string" && typeof b === "string") {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
return rowsToSort;
}
var sortKey = this.$scope.sortColumn,
sortDirectionMultiplier;
if (this.$scope.sortDirection === 'asc') {
sortDirectionMultiplier = 1;
} else if (this.$scope.sortDirection === 'desc') {
sortDirectionMultiplier = -1;
}
return rowsToSort.slice(0).sort(function(a, b) {
//If the values to compare can be compared as
// numbers, do so. String comparison of number
// values can cause inconsistencies
var valA = isNaN(a[sortKey].text) ? a[sortKey].text : parseFloat(a[sortKey].text),
valB = isNaN(b[sortKey].text) ? b[sortKey].text : parseFloat(b[sortKey].text);
return genericComparator(valA, valB) *
sortDirectionMultiplier;
});
};
/**
* 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.findLargestRow = function(rows) {
var largestRow = rows.reduce(function (largestRow, row) {
Object.keys(row).forEach(function (key) {
var currentColumn = row[key].text,
currentColumnLength =
(currentColumn && currentColumn.length) ?
currentColumn.length :
currentColumn,
largestColumn = largestRow[key].text,
largestColumnLength =
(largestColumn && largestColumn.length) ?
largestColumn.length :
largestColumn;
if (currentColumnLength > largestColumnLength) {
largestRow[key] = JSON.parse(JSON.stringify(row[key]));
}
});
return largestRow;
}, JSON.parse(JSON.stringify(rows[0] || {})));
// Pad with characters to accomodate variable-width fonts,
// and remove characters that would allow word-wrapping.
largestRow = JSON.parse(JSON.stringify(largestRow));
Object.keys(largestRow).forEach(function(key) {
var padCharacters,
i;
largestRow[key].text = String(largestRow[key].text);
padCharacters = largestRow[key].text.length / 10;
for (i = 0; i < padCharacters; i++) {
largestRow[key].text = largestRow[key].text + 'W';
}
largestRow[key].text = largestRow[key].text
.replace(/[ \-_]/g, 'W');
});
return largestRow;
};
MCTTableController.prototype.resize = function (){
var largestRow = this.findLargestRow(this.$scope.displayRows);
this.$scope.visibleRows = [
{
rowIndex: 0,
offsetY: undefined,
contents: largestRow
}
];
this.$timeout(this.setElementSizes.bind(this));
};
/**
* Update rows with new data. If filtering is enabled, rows
* will be sorted before display.
*/
MCTTableController.prototype.updateRows = function (newRows) {
var displayRows = newRows;
this.$scope.visibleRows = [];
this.$scope.overrideRowPositioning = false;
if (!this.$scope.displayHeaders) {
return;
}
if (this.$scope.enableFilter) {
displayRows = this.filterRows(displayRows);
}
if (this.$scope.enableSort) {
displayRows = this.sortRows(displayRows);
}
this.$scope.displayRows = displayRows;
this.resize();
};
/**
* Filter rows.
*/
MCTTableController.prototype.filterRows = function(rowsToFilter) {
var filters = {},
self = this;
/**
* Returns true if row matches all filters.
*/
function matchRow(filters, row) {
return Object.keys(filters).every(function(key) {
if (!row[key]) {
return false;
}
var testVal = String(row[key].text).toLowerCase();
return testVal.indexOf(filters[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));
};
return MCTTableController;
}
);

View File

@ -0,0 +1,94 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define*/
define(
[],
function () {
"use strict";
/**
* 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;
$scope.columnsForm = {};
this.domainObject.getCapability('mutation').listen(function (model) {
self.populateForm(model);
});
$scope.$watchCollection('configuration.table.columns', function(columns){
if (columns){
self.domainObject.useCapability('mutation', function(model) {
model.configuration.table.columns = columns;
});
self.domainObject.getCapability('persistence').persist();
}
});
}
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;
}
);

View File

@ -0,0 +1,189 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define*/
/**
* This bundle adds a table view for displaying telemetry data.
* @namespace platform/features/table
*/
define(
[
'../TableConfiguration'
],
function (TableConfiguration) {
"use strict";
/**
* 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
* @param telemetryHandler
* @param telemetryFormatter
* @constructor
*/
function TelemetryTableController(
$scope,
telemetryHandler,
telemetryFormatter
) {
var self = this;
this.$scope = $scope;
this.columns = {}; //Range and Domain columns
this.handle = undefined;
//this.pending = false;
this.telemetryHandler = telemetryHandler;
this.table = new TableConfiguration($scope.domainObject, telemetryFormatter);
this.changeListeners = [];
$scope.rows = [];
// Subscribe to telemetry when a domain object becomes available
this.$scope.$watch('domainObject', function(domainObject){
if (!domainObject)
return;
self.subscribe();
self.registerChangeListeners();
});
// Unsubscribe when the plot is destroyed
this.$scope.$on("$destroy", this.destroy.bind(this));
}
TelemetryTableController.prototype.registerChangeListeners = function() {
//Defer registration of change listeners until domain object is
// available in order to avoid race conditions
this.changeListeners.forEach(function (listener) {
return listener && listener();
});
this.changeListeners = [];
// When composition changes, re-subscribe to the various
// telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection('domainObject.getModel().composition', this.subscribe.bind(this)));
//Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.subscribe.bind(this)));
};
/**
* Release the current subscription (called when scope is destroyed)
*/
TelemetryTableController.prototype.destroy = function () {
if (this.handle) {
this.handle.unsubscribe();
this.handle = undefined;
}
};
/**
Create a new subscription. This is called when
*/
TelemetryTableController.prototype.subscribe = function() {
if (this.handle) {
this.handle.unsubscribe();
}
this.$scope.rows = [];
//Noop because not supporting realtime data right now
function noop(){
}
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject,
noop,
true // Lossless
);
this.handle.request({}, this.addHistoricalData.bind(this));
this.setup();
};
/**
* Add any historical data available
*/
TelemetryTableController.prototype.addHistoricalData = function(domainObject, series) {
var i;
for (i=0; i < series.getPointCount(); i++) {
this.updateRows(domainObject, this.handle.makeDatum(domainObject, series, i));
}
};
/**
* Setup table columns based on domain object metadata
*/
TelemetryTableController.prototype.setup = function() {
var handle = this.handle,
table = this.table,
self = this;
if (handle) {
handle.promiseTelemetryObjects().then(function (objects) {
table.buildColumns(handle.getMetadata());
self.filterColumns();
// When table column configuration changes, (due to being
// selected or deselected), filter columns appropriately.
self.changeListeners.push(self.$scope.$watchCollection(
'domainObject.getModel().configuration.table.columns',
self.filterColumns.bind(self)
));
});
}
};
/**
* Add data to rows
* @param object The object for which data is available (table may
* be composed of multiple objects)
* @param datum The data received from the telemetry source
*/
TelemetryTableController.prototype.updateRows = function (object, datum) {
this.$scope.rows.push(this.table.getRowValues(object, datum));
};
/**
* When column configuration changes, update the visible headers
* accordingly.
*/
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
if (!columnConfig){
columnConfig = this.table.getColumnConfiguration();
this.table.saveColumnConfiguration(columnConfig);
}
//Populate headers with visible columns (determined by configuration)
this.$scope.headers = Object.keys(columnConfig).filter(function(column) {
return columnConfig[column];
});
};
return TelemetryTableController;
}
);

View File

@ -0,0 +1,24 @@
/*global define*/
define(
["../controllers/MCTTableController"],
function (MCTTableController) {
"use strict";
function MCTTable($timeout) {
return {
restrict: "E",
templateUrl: "platform/features/table/res/templates/mct-data-table.html",
controller: ['$scope', '$timeout', '$element', MCTTableController],
scope: {
headers: "=",
rows: "=",
enableFilter: "=?",
enableSort: "=?"
},
};
}
return MCTTable;
}
);

View File

@ -0,0 +1,84 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
"use strict";
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A domain column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getDomainValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(testMetadata, mockFormatter);
});
it("reports a column header from domain metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
xit("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
xit("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,58 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/NameColumn"],
function (NameColumn) {
"use strict";
describe("A name column", function () {
var mockDomainObject,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel" ]
);
mockDomainObject.getModel.andReturn({
name: "Test object name"
});
column = new NameColumn();
});
it("reports a column header", function () {
expect(column.getTitle()).toEqual("Name");
});
it("looks up name from an object's model", function () {
expect(column.getValue(mockDomainObject).text)
.toEqual("Test object name");
});
});
}
);

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
"use strict";
var TEST_RANGE_VALUE = "some formatted range value";
describe("A range column", function () {
var testDatum,
testMetadata,
mockFormatter,
mockDomainObject,
column;
beforeEach(function () {
testDatum = { testKey: 123, otherKey: 456 };
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel", "getCapability" ]
);
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn(testMetadata, mockFormatter);
});
it("reports a column header from range metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("formats range values as numbers", function () {
expect(column.getValue(mockDomainObject, testDatum).text)
.toEqual(TEST_RANGE_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(testDatum.testKey);
expect(mockFormatter.formatDomainValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,197 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
define(
[
"../src/TableConfiguration",
"../src/DomainColumn"
],
function (Table, DomainColumn) {
"use strict";
describe("A table", function () {
var mockDomainObject,
mockTelemetryFormatter,
table,
mockModel;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj('domainObject',
['getModel', 'useCapability', 'getCapability']
);
mockModel = {};
mockDomainObject.getModel.andReturn(mockModel);
mockTelemetryFormatter = jasmine.createSpyObj('telemetryFormatter',
[
'formatDomainValue',
'formatRangeValue'
]);
mockTelemetryFormatter.formatDomainValue.andCallFake(function(valueIn){
return valueIn;
});
mockTelemetryFormatter.formatRangeValue.andCallFake(function(valueIn){
return valueIn;
});
table = new Table(mockDomainObject, mockTelemetryFormatter);
});
it("Add column with no index adds new column to the end", function () {
var firstColumn = {title: 'First Column'},
secondColumn = {title: 'Second Column'},
thirdColumn = {title: 'Third Column'};
table.addColumn(firstColumn);
table.addColumn(secondColumn);
table.addColumn(thirdColumn);
expect(table.columns).toBeDefined();
expect(table.columns.length).toBe(3);
expect(table.columns[0]).toBe(firstColumn);
expect(table.columns[1]).toBe(secondColumn);
expect(table.columns[2]).toBe(thirdColumn);
});
it("Add column with index adds new column at the specified" +
" position", function () {
var firstColumn = {title: 'First Column'},
secondColumn = {title: 'Second Column'},
thirdColumn = {title: 'Third Column'};
table.addColumn(firstColumn);
table.addColumn(thirdColumn);
table.addColumn(secondColumn, 1);
expect(table.columns).toBeDefined();
expect(table.columns.length).toBe(3);
expect(table.columns[0]).toBe(firstColumn);
expect(table.columns[1]).toBe(secondColumn);
expect(table.columns[2]).toBe(thirdColumn);
});
describe("Building columns from telemetry metadata", function() {
var metadata = [{
ranges: [
{
name: 'Range 1',
key: 'range1'
},
{
name: 'Range 2',
key: 'range2'
}
],
domains: [
{
name: 'Domain 1',
key: 'domain1',
format: 'utc'
},
{
name: 'Domain 2',
key: 'domain2',
format: 'utc'
}
]
}];
beforeEach(function() {
table.buildColumns(metadata);
});
it("populates the columns attribute", function() {
expect(table.columns.length).toBe(4);
});
it("Build columns populates columns with domains to the left", function() {
expect(table.columns[0] instanceof DomainColumn).toBeTruthy();
expect(table.columns[1] instanceof DomainColumn).toBeTruthy();
expect(table.columns[2] instanceof DomainColumn).toBeFalsy();
});
it("Produces headers for each column based on title", function() {
var headers,
firstColumn = table.columns[0];
spyOn(firstColumn, 'getTitle');
headers = table.getHeaders();
expect(headers.length).toBe(4);
expect(firstColumn.getTitle).toHaveBeenCalled();
});
it("Provides a default configuration with all columns" +
" visible", function() {
var configuration = table.getColumnConfiguration();
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.getColumnConfiguration();
expect(tableConfig).toBeDefined();
expect(tableConfig['Range 1']).toBe(false);
});
describe('retrieving row values', function () {
var datum,
rowValues;
beforeEach(function() {
datum = {
'range1': 'range 1 value',
'range2': 'range 2 value',
'domain1': 0,
'domain2': 1
};
rowValues = table.getRowValues(mockDomainObject, datum);
});
it("Returns a value for every column", function() {
expect(rowValues['Range 1'].text).toBeDefined();
expect(rowValues['Range 1'].text).toEqual('range 1' +
' value');
});
it("Uses the telemetry formatter to appropriately format" +
" telemetry values", function() {
expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled();
});
});
});
});
}
);

View File

@ -0,0 +1,155 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
define(
[
"../../src/controllers/MCTTableController"
],
function (MCTTableController) {
"use strict";
describe('The MCTTable Controller', function() {
var controller,
mockScope,
watches,
mockTimeout,
mockElement;
function promise(value) {
return {
then: function (callback){
return promise(callback(value));
}
};
}
beforeEach(function() {
watches = {};
mockScope = jasmine.createSpyObj('scope', [
'$watchCollection'
]);
mockScope.$watchCollection.andCallFake(function(event, callback) {
watches[event] = callback;
});
mockElement = jasmine.createSpyObj('element', [
'find',
'on'
]);
mockElement.find.andReturn(mockElement);
mockScope.displayHeaders = true;
mockTimeout = jasmine.createSpy('$timeout');
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
});
it('Reacts to changes to filters, headers, and rows', function() {
expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('headers', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('rows', jasmine.any(Function));
});
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'}
}
];
});
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.updateRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
expect(mockScope.displayRows).toEqual(testRows);
});
describe('sorting', function() {
var 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');
});
});
});
});
});

View File

@ -0,0 +1,105 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
define(
[
"../../src/controllers/TableOptionsController"
],
function (TableOptionsController) {
"use strict";
describe('The Table Options Controller', function() {
var mockDomainObject,
mockCapability,
controller,
mockScope;
function promise(value) {
return {
then: function (callback){
return promise(callback(value));
}
};
}
beforeEach(function() {
mockCapability = jasmine.createSpyObj('mutationCapability', [
'listen'
]);
mockDomainObject = jasmine.createSpyObj('domainObject', [
'getCapability'
]);
mockDomainObject.getCapability.andReturn(mockCapability);
mockScope = jasmine.createSpyObj('scope', [
'$watchCollection'
]);
mockScope.domainObject = mockDomainObject;
controller = new TableOptionsController(mockScope);
});
it('Registers a listener for mutation events on the object', function() {
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);
});
});
});
});

View File

@ -0,0 +1,222 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
define(
[
"../../src/controllers/TelemetryTableController"
],
function (TableController) {
"use strict";
describe('The Table Controller', function() {
var mockScope,
mockTelemetryHandler,
mockTelemetryHandle,
mockTelemetryFormatter,
mockDomainObject,
mockTable,
mockConfiguration,
watches,
controller;
function promise(value) {
return {
then: function (callback){
return promise(callback(value));
}
};
}
beforeEach(function() {
watches = {};
mockScope = jasmine.createSpyObj('scope', [
'$on',
'$watch',
'$watchCollection'
]);
mockScope.$on.andCallFake(function(expression, callback){
watches[expression] = callback;
});
mockScope.$watch.andCallFake(function(expression, callback){
watches[expression] = callback;
});
mockScope.$watchCollection.andCallFake(function(expression, callback){
watches[expression] = callback;
});
mockConfiguration = {
'range1': true,
'range2': true,
'domain1': true
};
mockTable = jasmine.createSpyObj('table',
[
'buildColumns',
'getColumnConfiguration',
'getRowValues',
'saveColumnConfiguration'
]
);
mockTable.columns = [];
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
mockDomainObject= jasmine.createSpyObj('domainObject', [
'getCapability',
'useCapability',
'getModel'
]);
mockDomainObject.getModel.andReturn({});
mockScope.domainObject = mockDomainObject;
mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
'request',
'promiseTelemetryObjects',
'getMetadata',
'unsubscribe',
'makeDatum'
]);
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
'handle'
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});
it('subscribes to telemetry handler for telemetry updates', function() {
controller.subscribe();
expect(mockTelemetryHandler.handle).toHaveBeenCalled();
expect(mockTelemetryHandle.request).toHaveBeenCalled();
});
it('Unsubscribes from telemetry when scope is destroyed',function() {
controller.handle = mockTelemetryHandle;
watches.$destroy();
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
});
describe('the controller makes use of the table', function() {
it('to create column definitions from telemetry' +
' metadata', function() {
controller.setup();
expect(mockTable.buildColumns).toHaveBeenCalled();
});
it('to create column configuration, which is written to the' +
' object model', function() {
var mockModel = {};
controller.setup();
expect(mockTable.getColumnConfiguration).toHaveBeenCalled();
expect(mockTable.saveColumnConfiguration).toHaveBeenCalled();
});
});
it('updates the rows on scope when historical telemetry is received', function(){
var mockSeries = {
getPointCount: function() {
return 5;
},
getDomainValue: function() {
return 'Domain Value';
},
getRangeValue: function() {
return 'Range Value';
}
},
mockRow = {'domain': 'Domain Value', 'range': 'Range' +
' Value'};
mockTelemetryHandle.makeDatum.andCallFake(function(){
return mockRow;
});
mockTable.getRowValues.andReturn(mockRow);
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
it('filters the visible columns based on configuration', function(){
controller.filterColumns();
expect(controller.$scope.headers.length).toBe(3);
expect(controller.$scope.headers[2]).toEqual('domain1');
mockConfiguration.domain1 = false;
controller.filterColumns();
expect(controller.$scope.headers.length).toBe(2);
expect(controller.$scope.headers[2]).toBeUndefined();
});
describe('creates event listeners', function(){
beforeEach(function() {
spyOn(controller,'subscribe');
spyOn(controller, 'filterColumns');
});
it('triggers telemetry subscription update when domain' +
' object changes', function() {
controller.registerChangeListeners();
//'watches' object is populated by fake scope watch and
// watchCollection functions defined above
expect(watches.domainObject).toBeDefined();
watches.domainObject(mockDomainObject);
expect(controller.subscribe).toHaveBeenCalled();
});
it('triggers telemetry subscription update when domain' +
' object composition changes', function() {
controller.registerChangeListeners();
expect(watches['domainObject.getModel().composition']).toBeDefined();
watches['domainObject.getModel().composition']();
expect(controller.subscribe).toHaveBeenCalled();
});
it('triggers telemetry subscription update when time' +
' conductor bounds change', function() {
controller.registerChangeListeners();
expect(watches['telemetry:display:bounds']).toBeDefined();
watches['telemetry:display:bounds']();
expect(controller.subscribe).toHaveBeenCalled();
});
it('triggers refiltering of the columns when configuration' +
' changes', function() {
controller.setup();
expect(watches['domainObject.getModel().configuration.table.columns']).toBeDefined();
watches['domainObject.getModel().configuration.table.columns']();
expect(controller.filterColumns).toHaveBeenCalled();
});
});
});
}
);