mirror of
https://github.com/nasa/openmct.git
synced 2025-01-18 02:39:56 +00:00
[Tables] Tests and style fixes
This commit is contained in:
parent
ef8efbd53d
commit
a3311e4c57
@ -22,8 +22,7 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/UTCTimeSystem",
|
"./src/UTCTimeSystem",
|
||||||
'legacyRegistry',
|
"legacyRegistry"
|
||||||
'openmct'
|
|
||||||
], function (
|
], function (
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<div ng-controller="TelemetryTableController as tableController"
|
<div ng-controller="TelemetryTableController as tableController"
|
||||||
ng-class="{'loading': loading}">
|
ng-class="{'loading': loading}">
|
||||||
Request time: {{tableController.lastRequestTime | date : 'HH:mm:ss:sss'}}
|
|
||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
||||||
|
@ -52,8 +52,10 @@ define(
|
|||||||
metadata.forEach(function (metadatum) {
|
metadata.forEach(function (metadatum) {
|
||||||
var formatter = telemetryApi.getValueFormatter(metadatum);
|
var formatter = telemetryApi.getValueFormatter(metadatum);
|
||||||
|
|
||||||
self.addColumn({
|
self.columns.push({
|
||||||
metadata: metadatum,
|
getKey: function () {
|
||||||
|
return metadatum.key;
|
||||||
|
},
|
||||||
getTitle: function () {
|
getTitle: function () {
|
||||||
return metadatum.name;
|
return metadatum.name;
|
||||||
},
|
},
|
||||||
@ -62,13 +64,16 @@ define(
|
|||||||
var alarm = isValueColumn &&
|
var alarm = isValueColumn &&
|
||||||
limitEvaluator &&
|
limitEvaluator &&
|
||||||
limitEvaluator.evaluate(telemetryDatum, metadatum);
|
limitEvaluator.evaluate(telemetryDatum, metadatum);
|
||||||
|
var value = {
|
||||||
return {
|
|
||||||
cssClass: alarm && alarm.cssClass,
|
|
||||||
text: formatter ? formatter.format(telemetryDatum[metadatum.key])
|
text: formatter ? formatter.format(telemetryDatum[metadatum.key])
|
||||||
: telemetryDatum[metadatum.key],
|
: telemetryDatum[metadatum.key],
|
||||||
value: telemetryDatum[metadatum.key]
|
value: telemetryDatum[metadatum.key]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (alarm) {
|
||||||
|
value.cssClass = alarm.cssClass;
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -76,29 +81,6 @@ define(
|
|||||||
return this;
|
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
|
* Get a simple list of column titles
|
||||||
* @returns {Array} The titles of the columns
|
* @returns {Array} The titles of the columns
|
||||||
@ -118,16 +100,15 @@ define(
|
|||||||
* title, and the value is the formatted value from the provided datum.
|
* title, and the value is the formatted value from the provided datum.
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.getRowValues = function (limitEvaluator, datum) {
|
TableConfiguration.prototype.getRowValues = function (limitEvaluator, datum) {
|
||||||
var self = this;
|
|
||||||
return this.columns.reduce(function (rowObject, column, i) {
|
return this.columns.reduce(function (rowObject, column, i) {
|
||||||
var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1),
|
var columnTitle = column.getTitle() || 'Column ' + (i + 1),
|
||||||
columnValue = column.getValue(datum, limitEvaluator);
|
columnValue = column.getValue(datum, limitEvaluator);
|
||||||
|
|
||||||
if (columnValue !== undefined && columnValue.text === undefined) {
|
if (columnValue !== undefined && columnValue.text === undefined) {
|
||||||
columnValue.text = '';
|
columnValue.text = '';
|
||||||
}
|
}
|
||||||
// Don't replace something with nothing.
|
// Don't replace something with nothing.
|
||||||
// This occurs when there are multiple columns with the
|
// This occurs when there are multiple columns with the same
|
||||||
// column title
|
// column title
|
||||||
if (rowObject[columnTitle] === undefined ||
|
if (rowObject[columnTitle] === undefined ||
|
||||||
rowObject[columnTitle].text === undefined ||
|
rowObject[columnTitle].text === undefined ||
|
||||||
|
@ -26,6 +26,10 @@ define(
|
|||||||
'EventEmitter'
|
'EventEmitter'
|
||||||
],
|
],
|
||||||
function (_, EventEmitter) {
|
function (_, EventEmitter) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
function TelemetryCollection() {
|
function TelemetryCollection() {
|
||||||
EventEmitter.call(this, arguments);
|
EventEmitter.call(this, arguments);
|
||||||
this.telemetry = [];
|
this.telemetry = [];
|
||||||
@ -41,15 +45,22 @@ define(
|
|||||||
|
|
||||||
TelemetryCollection.prototype = Object.create(EventEmitter.prototype);
|
TelemetryCollection.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
TelemetryCollection.prototype.iteratee = function (element) {
|
TelemetryCollection.prototype.iteratee = function (item) {
|
||||||
return _.get(element, this.sortField);
|
return _.get(item, this.sortField);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is optimized for ticking - it assumes that start and end bounds
|
* This function is optimized for ticking - it assumes that start and end
|
||||||
* will only increase and as such this cannot be used for decreasing bounds changes.
|
* bounds will only increase and as such this cannot be used for decreasing
|
||||||
* For arbitrary bounds changes, it's assumed that a telemetry requery is performed anyway, and the
|
* bounds changes.
|
||||||
* collection is cleared and repopulated.
|
*
|
||||||
|
* 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
|
* @param bounds
|
||||||
*/
|
*/
|
||||||
TelemetryCollection.prototype.bounds = function (bounds) {
|
TelemetryCollection.prototype.bounds = function (bounds) {
|
||||||
@ -57,56 +68,92 @@ define(
|
|||||||
var endChanged = this.lastBounds.end !== bounds.end;
|
var endChanged = this.lastBounds.end !== bounds.end;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var endIndex = 0;
|
var endIndex = 0;
|
||||||
var discarded = undefined;
|
var discarded;
|
||||||
var added = undefined;
|
var added;
|
||||||
|
var testValue;
|
||||||
|
|
||||||
|
// If collection is not sorted by a time field, we cannot respond to
|
||||||
|
// bounds events
|
||||||
|
if (this.sortField === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (startChanged) {
|
if (startChanged) {
|
||||||
var testValue = _.set({}, this.sortField, bounds.start);
|
testValue = _.set({}, this.sortField, bounds.start);
|
||||||
// Calculate the new index of the first element within the bounds
|
// Calculate the new index of the first item within the bounds
|
||||||
startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField);
|
startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField);
|
||||||
discarded = this.telemetry.splice(0, startIndex);
|
discarded = this.telemetry.splice(0, startIndex);
|
||||||
}
|
}
|
||||||
if (endChanged) {
|
if (endChanged) {
|
||||||
var testValue = _.set({}, this.sortField, bounds.end);
|
testValue = _.set({}, this.sortField, bounds.end);
|
||||||
// Calculate the new index of the last element in bounds
|
// Calculate the new index of the last item in bounds
|
||||||
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
||||||
added = this.highBuffer.splice(0, endIndex);
|
added = this.highBuffer.splice(0, endIndex);
|
||||||
this.telemetry = this.telemetry.concat(added);
|
this.telemetry = this.telemetry.concat(added);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discarded && discarded.length > 0) {
|
if (discarded && discarded.length > 0) {
|
||||||
|
/**
|
||||||
|
* A `discarded` event is thrown 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);
|
this.emit('discarded', discarded);
|
||||||
}
|
}
|
||||||
if (added && added.length > 0) {
|
if (added && added.length > 0) {
|
||||||
|
/**
|
||||||
|
* An `added` event is thrown 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);
|
this.emit('added', added);
|
||||||
}
|
}
|
||||||
this.lastBounds = bounds;
|
this.lastBounds = bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
TelemetryCollection.prototype.inBounds = function (element) {
|
/**
|
||||||
|
* Determines is a given telemetry datum is within the bounds currently
|
||||||
|
* defined for this telemetry collection.
|
||||||
|
* @private
|
||||||
|
* @param datum
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
TelemetryCollection.prototype.inBounds = function (datum) {
|
||||||
var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined);
|
var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined);
|
||||||
var withinBounds =
|
var withinBounds =
|
||||||
_.get(element, this.sortField) >= this.lastBounds.start &&
|
_.get(datum, this.sortField) >= this.lastBounds.start &&
|
||||||
_.get(element, this.sortField) <= this.lastBounds.end;
|
_.get(datum, this.sortField) <= this.lastBounds.end;
|
||||||
|
|
||||||
return noBoundsDefined || withinBounds;
|
return noBoundsDefined || withinBounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Adds an individual item to the collection. Used internally only
|
||||||
* @private
|
* @private
|
||||||
* @param element
|
* @param item
|
||||||
*/
|
*/
|
||||||
TelemetryCollection.prototype.addOne = function (element) {
|
TelemetryCollection.prototype.addOne = function (item) {
|
||||||
var isDuplicate = false;
|
var isDuplicate = false;
|
||||||
var boundsDefined = this.lastBounds && (this.lastBounds.start && this.lastBounds.end);
|
var boundsDefined = this.lastBounds &&
|
||||||
|
(this.lastBounds.start !== undefined && this.lastBounds.end !== undefined);
|
||||||
var array;
|
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.
|
// 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
|
// Data in the high buffer will be re-evaluated for possible insertion on next tick
|
||||||
|
|
||||||
if (boundsDefined) {
|
if (boundsDefined) {
|
||||||
var boundsHigh = _.get(element, this.sortField) > this.lastBounds.end;
|
boundsHigh = _.get(item, this.sortField) > this.lastBounds.end;
|
||||||
var boundsLow = _.get(element, this.sortField) < this.lastBounds.start;
|
boundsLow = _.get(item, this.sortField) < this.lastBounds.start;
|
||||||
|
|
||||||
if (!boundsHigh && !boundsLow) {
|
if (!boundsHigh && !boundsLow) {
|
||||||
array = this.telemetry;
|
array = this.telemetry;
|
||||||
@ -119,26 +166,26 @@ define(
|
|||||||
|
|
||||||
// If out of bounds low, disregard data
|
// If out of bounds low, disregard data
|
||||||
if (!boundsLow) {
|
if (!boundsLow) {
|
||||||
|
|
||||||
// Going to check for duplicates. Bound the search problem to
|
// Going to check for duplicates. Bound the search problem to
|
||||||
// elements around the given time. Use sortedIndex because it
|
// items around the given time. Use sortedIndex because it
|
||||||
// employs a binary search which is O(log n). Can use binary search
|
// employs a binary search which is O(log n). Can use binary search
|
||||||
// based on time stamp because the array is guaranteed ordered due
|
// based on time stamp because the array is guaranteed ordered due
|
||||||
// to sorted insertion.
|
// to sorted insertion.
|
||||||
|
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||||
var startIx = _.sortedIndex(array, element, this.sortField);
|
|
||||||
|
|
||||||
if (startIx !== array.length) {
|
if (startIx !== array.length) {
|
||||||
var endIx = _.sortedLastIndex(array, element, this.sortField);
|
var endIx = _.sortedLastIndex(array, item, this.sortField);
|
||||||
|
|
||||||
// Create an array of potential dupes, based on having the
|
// Create an array of potential dupes, based on having the
|
||||||
// same time stamp
|
// same time stamp
|
||||||
var potentialDupes = array.slice(startIx, endIx + 1);
|
var potentialDupes = array.slice(startIx, endIx + 1);
|
||||||
// Search potential dupes for exact dupe
|
// Search potential dupes for exact dupe
|
||||||
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, element)) > -1;
|
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
array.splice(startIx, 0, element);
|
array.splice(startIx, 0, item);
|
||||||
|
|
||||||
//Return true if it was added and in bounds
|
//Return true if it was added and in bounds
|
||||||
return array === this.telemetry;
|
return array === this.telemetry;
|
||||||
@ -147,28 +194,60 @@ define(
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
TelemetryCollection.prototype.addAll = function (elements) {
|
/**
|
||||||
var added = elements.filter(this.addOne);
|
* 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.emit('added', added);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Todo: addAll for initial historical data
|
/**
|
||||||
TelemetryCollection.prototype.add = function (element) {
|
* Clears the contents of the telemetry collection
|
||||||
if (this.addOne(element)){
|
*/
|
||||||
this.emit('added', [element]);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TelemetryCollection.prototype.clear = function () {
|
TelemetryCollection.prototype.clear = function () {
|
||||||
this.telemetry = [];
|
this.telemetry = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the telemetry collection based on the provided sort field
|
||||||
|
* specifier.
|
||||||
|
* @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) {
|
TelemetryCollection.prototype.sort = function (sortField) {
|
||||||
this.sortField = sortField;
|
this.sortField = sortField;
|
||||||
|
if (sortField !== undefined) {
|
||||||
this.telemetry = _.sortBy(this.telemetry, this.iteratee);
|
this.telemetry = _.sortBy(this.telemetry, this.iteratee);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return TelemetryCollection;
|
return TelemetryCollection;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
['zepto'],
|
[
|
||||||
function ($) {
|
'zepto',
|
||||||
|
'lodash'
|
||||||
|
],
|
||||||
|
function ($, _) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A controller for the MCTTable directive. Populates scope with
|
* A controller for the MCTTable directive. Populates scope with
|
||||||
@ -134,7 +137,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
|
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
|
||||||
if (newColumn !== oldColumn) {
|
if (newColumn !== oldColumn) {
|
||||||
$scope.toggleSort(newColumn)
|
$scope.toggleSort(newColumn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,7 +175,7 @@ define(
|
|||||||
delete this.$scope;
|
delete this.$scope;
|
||||||
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
}
|
||||||
|
|
||||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
MCTTableController.prototype.destroyConductorListeners = function () {
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||||
@ -252,7 +255,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.onScroll = function (event) {
|
MCTTableController.prototype.onScroll = function (event) {
|
||||||
requestAnimationFrame(function () {
|
this.$window.requestAnimationFrame(function () {
|
||||||
this.setVisibleRows();
|
this.setVisibleRows();
|
||||||
this.digest();
|
this.digest();
|
||||||
|
|
||||||
@ -344,18 +347,11 @@ define(
|
|||||||
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
|
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
|
||||||
.rowIndex === end) {
|
.rowIndex === end) {
|
||||||
return this.digest();
|
return this.digest();
|
||||||
//return Promise.resolve(); // don't update if no changes are required.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Set visible rows from display rows, based on calculated offset.
|
//Set visible rows from display rows, based on calculated offset.
|
||||||
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
||||||
.map(function (row, i) {
|
.map(function (row, i) {
|
||||||
/* var formattedRow = JSON.parse(JSON.stringify(row));
|
|
||||||
if (self.$scope.formatCell) {
|
|
||||||
Object.keys(formattedRow).forEach(function (header) {
|
|
||||||
formattedRow[header].text = self.$scope.formatCell(header, row[header].text);
|
|
||||||
});
|
|
||||||
} */
|
|
||||||
return {
|
return {
|
||||||
rowIndex: start + i,
|
rowIndex: start + i,
|
||||||
offsetY: ((start + i) * self.$scope.rowHeight) +
|
offsetY: ((start + i) * self.$scope.rowHeight) +
|
||||||
@ -585,19 +581,20 @@ define(
|
|||||||
MCTTableController.prototype.digest = function () {
|
MCTTableController.prototype.digest = function () {
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var self = this;
|
var self = this;
|
||||||
var requestAnimationFrame = this.$window.requestAnimationFrame;
|
var raf = this.$window.requestAnimationFrame;
|
||||||
|
var promise = this.digestPromise;
|
||||||
|
|
||||||
if (!this.digestPromise) {
|
if (!promise) {
|
||||||
this.digestPromise = new Promise(function (resolve) {
|
self.digestPromise = promise = new Promise(function (resolve) {
|
||||||
requestAnimationFrame(function() {
|
raf(function () {
|
||||||
scope.$digest();
|
scope.$digest();
|
||||||
delete self.digestPromise;
|
self.digestPromise = undefined;
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.digestPromise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -640,8 +637,10 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
||||||
this.resize(newRows)
|
return this.resize(newRows)
|
||||||
.then(this.setVisibleRows)
|
.then(function (rows) {
|
||||||
|
return this.setVisibleRows(rows);
|
||||||
|
}.bind(this))
|
||||||
//Timeout following setVisibleRows to allow digest to
|
//Timeout following setVisibleRows to allow digest to
|
||||||
// perform DOM changes, otherwise scrollTo won't work.
|
// perform DOM changes, otherwise scrollTo won't work.
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@ -692,6 +691,7 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Scroll the view to a given row index
|
||||||
* @param displayRowIndex {number} The index in the displayed rows
|
* @param displayRowIndex {number} The index in the displayed rows
|
||||||
* to scroll to.
|
* to scroll to.
|
||||||
*/
|
*/
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
/* global console*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This bundle adds a table view for displaying telemetry data.
|
* This bundle adds a table view for displaying telemetry data.
|
||||||
@ -28,10 +29,11 @@ define(
|
|||||||
[
|
[
|
||||||
'../TableConfiguration',
|
'../TableConfiguration',
|
||||||
'../../../../../src/api/objects/object-utils',
|
'../../../../../src/api/objects/object-utils',
|
||||||
'../TelemetryCollection'
|
'../TelemetryCollection',
|
||||||
|
'lodash'
|
||||||
|
|
||||||
],
|
],
|
||||||
function (TableConfiguration, objectUtils, TelemetryCollection) {
|
function (TableConfiguration, objectUtils, TelemetryCollection, _) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TableController is responsible for getting data onto the page
|
* The TableController is responsible for getting data onto the page
|
||||||
@ -46,6 +48,7 @@ define(
|
|||||||
$timeout,
|
$timeout,
|
||||||
openmct
|
openmct
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.$timeout = $timeout;
|
this.$timeout = $timeout;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
@ -55,14 +58,14 @@ define(
|
|||||||
* Initialization block
|
* Initialization block
|
||||||
*/
|
*/
|
||||||
this.columns = {}; //Range and Domain columns
|
this.columns = {}; //Range and Domain columns
|
||||||
this.deregisterListeners = [];
|
this.unobserveObject = undefined;
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
this.timeColumns = [];
|
this.timeColumns = [];
|
||||||
$scope.rows = [];
|
$scope.rows = [];
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
this.table = new TableConfiguration($scope.domainObject,
|
||||||
openmct);
|
openmct);
|
||||||
this.lastBounds = this.openmct.conductor.bounds();
|
this.lastBounds = this.openmct.conductor.bounds();
|
||||||
this.requestTime = 0;
|
this.lastRequestTime = 0;
|
||||||
this.telemetry = new TelemetryCollection();
|
this.telemetry = new TelemetryCollection();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -81,21 +84,26 @@ define(
|
|||||||
'changeBounds',
|
'changeBounds',
|
||||||
'setScroll',
|
'setScroll',
|
||||||
'addRowsToTable',
|
'addRowsToTable',
|
||||||
'removeRowsFromTable',
|
'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.getData();
|
||||||
this.registerChangeListeners();
|
this.registerChangeListeners();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
this.openmct.conductor.on('follow', this.setScroll);
|
|
||||||
this.setScroll(this.openmct.conductor.follow());
|
this.setScroll(this.openmct.conductor.follow());
|
||||||
|
|
||||||
this.telemetry.on('added', this.addRowsToTable);
|
|
||||||
this.telemetry.on('discarded', this.removeRowsFromTable);
|
|
||||||
|
|
||||||
this.$scope.$on("$destroy", this.destroy);
|
this.$scope.$on("$destroy", this.destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {boolean} scroll
|
||||||
|
*/
|
||||||
TelemetryTableController.prototype.setScroll = function (scroll) {
|
TelemetryTableController.prototype.setScroll = function (scroll) {
|
||||||
this.$scope.autoScroll = scroll;
|
this.$scope.autoScroll = scroll;
|
||||||
};
|
};
|
||||||
@ -103,16 +111,18 @@ define(
|
|||||||
/**
|
/**
|
||||||
* Based on the selected time system, find a matching domain column
|
* Based on the selected time system, find a matching domain column
|
||||||
* to sort by. By default will just match on key.
|
* to sort by. By default will just match on key.
|
||||||
* @param timeSystem
|
*
|
||||||
|
* @private
|
||||||
|
* @param {TimeSystem} timeSystem
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) {
|
TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) {
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var sortColumn = undefined;
|
var sortColumn;
|
||||||
scope.defaultSort = undefined;
|
scope.defaultSort = undefined;
|
||||||
|
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.table.columns.forEach(function (column) {
|
this.table.columns.forEach(function (column) {
|
||||||
if (column.metadata.key === timeSystem.metadata.key) {
|
if (column.getKey() === timeSystem.metadata.key) {
|
||||||
sortColumn = column;
|
sortColumn = column;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -124,38 +134,60 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach listeners to domain object to respond to changes due to
|
* Attaches listeners that respond to state change in domain object,
|
||||||
* composition, etc.
|
* conductor, and receipt of telemetry
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||||
this.deregisterListeners.forEach(function (deregister){
|
if (this.unobserveObject) {
|
||||||
deregister();
|
this.unobserveObject();
|
||||||
});
|
}
|
||||||
this.deregisterListeners = [];
|
|
||||||
|
|
||||||
this.deregisterListeners.push(
|
this.unobserveObject = this.openmct.objects.observe(this.newObject, "*",
|
||||||
this.openmct.objects.observe(this.newObject, "*",
|
|
||||||
function (domainObject) {
|
function (domainObject) {
|
||||||
this.newObject = domainObject;
|
this.newObject = domainObject;
|
||||||
this.getData();
|
this.getData();
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
|
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
|
||||||
this.openmct.conductor.on('bounds', this.changeBounds);
|
this.openmct.conductor.on('bounds', this.changeBounds);
|
||||||
|
this.openmct.conductor.on('follow', this.setScroll);
|
||||||
|
|
||||||
|
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) {
|
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
||||||
this.$scope.$broadcast('add:rows', 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) {
|
TelemetryTableController.prototype.removeRowsFromTable = function (rows) {
|
||||||
this.$scope.$broadcast('remove:rows', 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) {
|
TelemetryTableController.prototype.changeBounds = function (bounds) {
|
||||||
//console.log('bounds.end: ' + bounds.end);
|
|
||||||
var follow = this.openmct.conductor.follow();
|
var follow = this.openmct.conductor.follow();
|
||||||
var isTick = follow &&
|
var isTick = follow &&
|
||||||
bounds.start !== this.lastBounds.start &&
|
bounds.start !== this.lastBounds.start &&
|
||||||
@ -171,7 +203,7 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the current subscription (called when scope is destroyed)
|
* Clean controller, deregistering listeners etc.
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.destroy = function () {
|
TelemetryTableController.prototype.destroy = function () {
|
||||||
|
|
||||||
@ -182,11 +214,11 @@ define(
|
|||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
subscription();
|
subscription();
|
||||||
});
|
});
|
||||||
this.deregisterListeners.forEach(function (deregister){
|
|
||||||
deregister();
|
if (this.unobserveObject) {
|
||||||
});
|
this.unobserveObject();
|
||||||
|
}
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
this.deregisterListeners = [];
|
|
||||||
|
|
||||||
if (this.timeoutHandle) {
|
if (this.timeoutHandle) {
|
||||||
this.$timeout.cancel(this.timeoutHandle);
|
this.$timeout.cancel(this.timeoutHandle);
|
||||||
@ -200,9 +232,10 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* For given objects, populate column metadata and table headers.
|
||||||
* @private
|
* @private
|
||||||
* @param objects
|
* @param {module:openmct.DomainObject[]} objects the domain objects for
|
||||||
* @returns {*}
|
* which columns should be populated
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.loadColumns = function (objects) {
|
TelemetryTableController.prototype.loadColumns = function (objects) {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
@ -220,25 +253,28 @@ define(
|
|||||||
|
|
||||||
this.filterColumns();
|
this.filterColumns();
|
||||||
|
|
||||||
|
// Default to no sort on underlying telemetry collection. Sorting
|
||||||
|
// is necessary to do bounds filtering, but this is only possible
|
||||||
|
// if data matches selected time system
|
||||||
|
this.telemetry.sort(undefined);
|
||||||
|
|
||||||
var timeSystem = this.openmct.conductor.timeSystem();
|
var timeSystem = this.openmct.conductor.timeSystem();
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.sortByTimeSystem(timeSystem);
|
this.sortByTimeSystem(timeSystem);
|
||||||
}
|
}
|
||||||
if (!this.telemetry.sortColumn && domainColumns.length > 0) {
|
|
||||||
this.telemetry.sort(domainColumns[0].name + '.value');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return objects;
|
return objects;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Request telemetry data from an historical store for given objects.
|
||||||
* @private
|
* @private
|
||||||
* @param objects The domain objects to request telemetry for
|
* @param {object[]} The domain objects to request telemetry for
|
||||||
* @returns {*|{configFile}|app|boolean|Route|Object}
|
* @returns {Promise} resolved when historical data is available
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
||||||
|
var self = this;
|
||||||
var openmct = this.openmct;
|
var openmct = this.openmct;
|
||||||
var bounds = openmct.conductor.bounds();
|
var bounds = openmct.conductor.bounds();
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
@ -247,14 +283,21 @@ define(
|
|||||||
var requestTime = this.lastRequestTime = Date.now();
|
var requestTime = this.lastRequestTime = Date.now();
|
||||||
var telemetryCollection = this.telemetry;
|
var telemetryCollection = this.telemetry;
|
||||||
|
|
||||||
return new Promise(function (resolve, reject){
|
var promise = new Promise(function (resolve, reject) {
|
||||||
|
/*
|
||||||
|
* On completion of batched processing, set the rows on scope
|
||||||
|
*/
|
||||||
function finishProcessing() {
|
function finishProcessing() {
|
||||||
telemetryCollection.addAll(rowData);
|
telemetryCollection.add(rowData);
|
||||||
scope.rows = telemetryCollection.telemetry;
|
scope.rows = telemetryCollection.telemetry;
|
||||||
scope.loading = false;
|
scope.loading = false;
|
||||||
|
|
||||||
resolve(scope.rows);
|
resolve(scope.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process a batch of historical data
|
||||||
|
*/
|
||||||
function processData(historicalData, index, limitEvaluator) {
|
function processData(historicalData, index, limitEvaluator) {
|
||||||
if (index >= historicalData.length) {
|
if (index >= historicalData.length) {
|
||||||
processedObjects++;
|
processedObjects++;
|
||||||
@ -263,51 +306,57 @@ define(
|
|||||||
finishProcessing();
|
finishProcessing();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rowData = rowData.concat(historicalData.slice(index, index + this.batchSize)
|
rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
|
||||||
.map(this.table.getRowValues.bind(this.table, limitEvaluator)));
|
.map(self.table.getRowValues.bind(self.table, limitEvaluator)));
|
||||||
|
|
||||||
this.timeoutHandle = this.$timeout(processData.bind(
|
/*
|
||||||
this,
|
Use timeout to yield process to other UI activities. On
|
||||||
historicalData,
|
return, process next batch
|
||||||
index + this.batchSize,
|
*/
|
||||||
limitEvaluator
|
self.timeoutHandle = self.$timeout(function () {
|
||||||
));
|
processData(historicalData, index + self.batchSize, limitEvaluator);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTableRows(object, historicalData) {
|
function makeTableRows(object, historicalData) {
|
||||||
// Only process one request at a time
|
// Only process the most recent request
|
||||||
if (requestTime === this.lastRequestTime) {
|
if (requestTime === self.lastRequestTime) {
|
||||||
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
||||||
processData.call(this, historicalData, 0, limitEvaluator);
|
processData(historicalData, 0, limitEvaluator);
|
||||||
} else {
|
} else {
|
||||||
resolve(rowData);
|
resolve(rowData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the telemetry API to request telemetry for a given object
|
||||||
|
*/
|
||||||
function requestData(object) {
|
function requestData(object) {
|
||||||
return openmct.telemetry.request(object, {
|
return openmct.telemetry.request(object, {
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end
|
end: bounds.end
|
||||||
}).then(makeTableRows.bind(this, object))
|
}).then(makeTableRows.bind(undefined, object))
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
}
|
}
|
||||||
this.$timeout.cancel(this.timeoutHandle);
|
this.$timeout.cancel(this.timeoutHandle);
|
||||||
|
|
||||||
if (objects.length > 0) {
|
if (objects.length > 0) {
|
||||||
objects.forEach(requestData.bind(this));
|
objects.forEach(requestData);
|
||||||
} else {
|
} else {
|
||||||
scope.loading = false;
|
scope.loading = false;
|
||||||
resolve([]);
|
resolve([]);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Subscribe to real-time data for the given objects.
|
||||||
* @private
|
* @private
|
||||||
* @param objects
|
* @param {object[]} objects The objects to subscribe to.
|
||||||
* @returns {*}
|
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
|
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
@ -317,6 +366,8 @@ define(
|
|||||||
var maxRows = Number.MAX_VALUE;
|
var maxRows = Number.MAX_VALUE;
|
||||||
var limitEvaluator;
|
var limitEvaluator;
|
||||||
var added = false;
|
var added = false;
|
||||||
|
var scope = this.$scope;
|
||||||
|
var table = this.table;
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
subscription();
|
subscription();
|
||||||
@ -325,16 +376,16 @@ define(
|
|||||||
|
|
||||||
function newData(domainObject, datum) {
|
function newData(domainObject, datum) {
|
||||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
||||||
added = telemetryCollection.add(this.table.getRowValues(limitEvaluator, datum));
|
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
|
||||||
|
|
||||||
//Inform table that a new row has been added
|
//Inform table that a new row has been added
|
||||||
if (this.$scope.rows.length > maxRows) {
|
if (scope.rows.length > maxRows) {
|
||||||
this.$scope.$broadcast('remove:rows', this.$scope.rows[0]);
|
scope.$broadcast('remove:rows', scope.rows[0]);
|
||||||
this.$scope.rows.shift();
|
scope.rows.shift();
|
||||||
}
|
}
|
||||||
if (!this.$scope.loading && added) {
|
if (!scope.loading && added) {
|
||||||
this.$scope.$broadcast('add:row',
|
scope.$broadcast('add:row',
|
||||||
this.$scope.rows.length - 1);
|
scope.rows.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,6 +397,12 @@ define(
|
|||||||
return objects;
|
return objects;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 () {
|
TelemetryTableController.prototype.getData = function () {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
var compositionApi = this.openmct.composition;
|
var compositionApi = this.openmct.composition;
|
||||||
@ -359,7 +416,7 @@ define(
|
|||||||
|
|
||||||
function error(e) {
|
function error(e) {
|
||||||
scope.loading = false;
|
scope.loading = false;
|
||||||
console.error(e);
|
console.error(e.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterForTelemetry(objects) {
|
function filterForTelemetry(objects) {
|
||||||
@ -367,33 +424,29 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDomainObjects() {
|
function getDomainObjects() {
|
||||||
return new Promise(function (resolve, reject){
|
|
||||||
var objects = [newObject];
|
var objects = [newObject];
|
||||||
var composition = compositionApi.get(newObject);
|
var composition = compositionApi.get(newObject);
|
||||||
|
|
||||||
if (composition) {
|
if (composition) {
|
||||||
composition
|
return composition
|
||||||
.load()
|
.load()
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
return objects.concat(children);
|
return objects.concat(children);
|
||||||
})
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
} else {
|
|
||||||
return resolve(objects);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(objects);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.headers = [];
|
scope.headers = [];
|
||||||
scope.rows = [];
|
scope.rows = [];
|
||||||
|
|
||||||
getDomainObjects()
|
return getDomainObjects()
|
||||||
.then(filterForTelemetry)
|
.then(filterForTelemetry)
|
||||||
.then(this.loadColumns)
|
.then(this.loadColumns)
|
||||||
.then(this.subscribeToNewData)
|
.then(this.subscribeToNewData)
|
||||||
.then(this.getHistoricalData)
|
.then(this.getHistoricalData)
|
||||||
.catch(error)
|
.catch(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["../src/DomainColumn"],
|
|
||||||
function (DomainColumn) {
|
|
||||||
|
|
||||||
var TEST_DOMAIN_VALUE = "some formatted domain value";
|
|
||||||
|
|
||||||
describe("A domain column", function () {
|
|
||||||
var mockDatum,
|
|
||||||
testMetadata,
|
|
||||||
mockFormatter,
|
|
||||||
column;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
mockFormatter = jasmine.createSpyObj(
|
|
||||||
"formatter",
|
|
||||||
["formatDomainValue", "formatRangeValue"]
|
|
||||||
);
|
|
||||||
testMetadata = {
|
|
||||||
key: "testKey",
|
|
||||||
name: "Test Name",
|
|
||||||
format: "Test Format"
|
|
||||||
};
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when given a datum", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockDatum = {
|
|
||||||
testKey: "testKeyValue"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("looks up data from the given datum", function () {
|
|
||||||
expect(column.getValue(undefined, mockDatum))
|
|
||||||
.toEqual({ text: TEST_DOMAIN_VALUE });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses formatter to format domain values as requested", function () {
|
|
||||||
column.getValue(undefined, mockDatum);
|
|
||||||
expect(mockFormatter.formatDomainValue)
|
|
||||||
.toHaveBeenCalledWith("testKeyValue", "Test Format");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,56 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["../src/NameColumn"],
|
|
||||||
function (NameColumn) {
|
|
||||||
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["../src/RangeColumn"],
|
|
||||||
function (RangeColumn) {
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -22,13 +22,14 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"../src/TableConfiguration",
|
"../src/TableConfiguration"
|
||||||
"../src/DomainColumn"
|
|
||||||
],
|
],
|
||||||
function (Table, DomainColumn) {
|
function (Table) {
|
||||||
|
|
||||||
describe("A table", function () {
|
describe("A table", function () {
|
||||||
var mockDomainObject,
|
var mockDomainObject,
|
||||||
|
mockAPI,
|
||||||
|
mockTelemetryAPI,
|
||||||
mockTelemetryFormatter,
|
mockTelemetryFormatter,
|
||||||
table,
|
table,
|
||||||
mockModel;
|
mockModel;
|
||||||
@ -41,90 +42,63 @@ define(
|
|||||||
mockDomainObject.getModel.andReturn(mockModel);
|
mockDomainObject.getModel.andReturn(mockModel);
|
||||||
mockTelemetryFormatter = jasmine.createSpyObj('telemetryFormatter',
|
mockTelemetryFormatter = jasmine.createSpyObj('telemetryFormatter',
|
||||||
[
|
[
|
||||||
'formatDomainValue',
|
'format'
|
||||||
'formatRangeValue'
|
|
||||||
]);
|
]);
|
||||||
mockTelemetryFormatter.formatDomainValue.andCallFake(function (valueIn) {
|
mockTelemetryFormatter.format.andCallFake(function (valueIn) {
|
||||||
return valueIn;
|
|
||||||
});
|
|
||||||
mockTelemetryFormatter.formatRangeValue.andCallFake(function (valueIn) {
|
|
||||||
return valueIn;
|
return valueIn;
|
||||||
});
|
});
|
||||||
|
|
||||||
table = new Table(mockDomainObject, mockTelemetryFormatter);
|
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
||||||
});
|
'getValueFormatter'
|
||||||
|
]);
|
||||||
|
mockAPI = {
|
||||||
|
telemetry: mockTelemetryAPI
|
||||||
|
};
|
||||||
|
mockTelemetryAPI.getValueFormatter.andReturn(mockTelemetryFormatter);
|
||||||
|
|
||||||
it("Add column with no index adds new column to the end", function () {
|
table = new Table(mockDomainObject, mockAPI);
|
||||||
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 () {
|
describe("Building columns from telemetry metadata", function () {
|
||||||
var metadata = [{
|
var metadata = [
|
||||||
ranges: [
|
|
||||||
{
|
{
|
||||||
name: 'Range 1',
|
name: 'Range 1',
|
||||||
key: 'range1'
|
key: 'range1',
|
||||||
|
hints: {
|
||||||
|
y: 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Range 2',
|
name: 'Range 2',
|
||||||
key: 'range2'
|
key: 'range2',
|
||||||
|
hints: {
|
||||||
|
y: 2
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
domains: [
|
|
||||||
{
|
{
|
||||||
name: 'Domain 1',
|
name: 'Domain 1',
|
||||||
key: 'domain1',
|
key: 'domain1',
|
||||||
format: 'utc'
|
format: 'utc',
|
||||||
|
hints: {
|
||||||
|
x: 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Domain 2',
|
name: 'Domain 2',
|
||||||
key: 'domain2',
|
key: 'domain2',
|
||||||
format: 'utc'
|
format: 'utc',
|
||||||
|
hints: {
|
||||||
|
x: 2
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}];
|
];
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
table.populateColumns(metadata);
|
table.populateColumns(metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("populates columns", function () {
|
it("populates columns", function () {
|
||||||
expect(table.columns.length).toBe(5);
|
expect(table.columns.length).toBe(4);
|
||||||
});
|
|
||||||
|
|
||||||
it("Build columns populates columns with domains to the left", function () {
|
|
||||||
expect(table.columns[1] instanceof DomainColumn).toBeTruthy();
|
|
||||||
expect(table.columns[2] instanceof DomainColumn).toBeTruthy();
|
|
||||||
expect(table.columns[3] instanceof DomainColumn).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Produces headers for each column based on title", function () {
|
it("Produces headers for each column based on title", function () {
|
||||||
@ -133,7 +107,7 @@ define(
|
|||||||
|
|
||||||
spyOn(firstColumn, 'getTitle');
|
spyOn(firstColumn, 'getTitle');
|
||||||
headers = table.getHeaders();
|
headers = table.getHeaders();
|
||||||
expect(headers.length).toBe(5);
|
expect(headers.length).toBe(4);
|
||||||
expect(firstColumn.getTitle).toHaveBeenCalled();
|
expect(firstColumn.getTitle).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,23 +144,33 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
datum = {
|
datum = {
|
||||||
'range1': 'range 1 value',
|
'range1': 10,
|
||||||
'range2': 'range 2 value',
|
'range2': 20,
|
||||||
'domain1': 0,
|
'domain1': 0,
|
||||||
'domain2': 1
|
'domain2': 1
|
||||||
};
|
};
|
||||||
rowValues = table.getRowValues(mockDomainObject, datum);
|
var limitEvaluator = {
|
||||||
|
evaluate: function () {
|
||||||
|
return {
|
||||||
|
"cssClass": "alarm-class"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rowValues = table.getRowValues(limitEvaluator, datum);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns a value for every column", function () {
|
it("Returns a value for every column", function () {
|
||||||
expect(rowValues['Range 1'].text).toBeDefined();
|
expect(rowValues['Range 1'].text).toBeDefined();
|
||||||
expect(rowValues['Range 1'].text).toEqual('range 1' +
|
expect(rowValues['Range 1'].text).toEqual(10);
|
||||||
' value');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Uses the telemetry formatter to appropriately format" +
|
it("Applies appropriate css class if limit violated.", function () {
|
||||||
|
expect(rowValues['Range 1'].cssClass).toEqual("alarm-class");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Uses telemetry formatter to appropriately format" +
|
||||||
" telemetry values", function () {
|
" telemetry values", function () {
|
||||||
expect(mockTelemetryFormatter.formatRangeValue).toHaveBeenCalled();
|
expect(mockTelemetryFormatter.format).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
191
platform/features/table/test/TelemetryCollectionSpec.js
Normal file
191
platform/features/table/test/TelemetryCollectionSpec.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
"../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.mostRecentCall.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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -1,380 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../../src/controllers/HistoricalTableController"
|
|
||||||
],
|
|
||||||
function (TableController) {
|
|
||||||
|
|
||||||
describe('The Table Controller', function () {
|
|
||||||
var mockScope,
|
|
||||||
mockTelemetryHandler,
|
|
||||||
mockTelemetryHandle,
|
|
||||||
mockTelemetryFormatter,
|
|
||||||
mockDomainObject,
|
|
||||||
mockTable,
|
|
||||||
mockConfiguration,
|
|
||||||
mockAngularTimeout,
|
|
||||||
mockTimeoutHandle,
|
|
||||||
watches,
|
|
||||||
mockConductor,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
function promise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return promise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCallback(target, event) {
|
|
||||||
return target.calls.filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockTimeoutHandle = jasmine.createSpy("timeoutHandle");
|
|
||||||
mockAngularTimeout = jasmine.createSpy("$timeout");
|
|
||||||
mockAngularTimeout.andReturn(mockTimeoutHandle);
|
|
||||||
mockAngularTimeout.cancel = jasmine.createSpy("cancelTimeout");
|
|
||||||
|
|
||||||
mockConfiguration = {
|
|
||||||
'range1': true,
|
|
||||||
'range2': true,
|
|
||||||
'domain1': true
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTable = jasmine.createSpyObj('table',
|
|
||||||
[
|
|
||||||
'populateColumns',
|
|
||||||
'buildColumnConfiguration',
|
|
||||||
'getRowValues',
|
|
||||||
'saveColumnConfiguration'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTable.columns = [];
|
|
||||||
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
|
||||||
'getCapability',
|
|
||||||
'useCapability',
|
|
||||||
'getModel'
|
|
||||||
]);
|
|
||||||
mockDomainObject.getModel.andReturn({});
|
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
|
|
||||||
'request',
|
|
||||||
'promiseTelemetryObjects',
|
|
||||||
'getTelemetryObjects',
|
|
||||||
'getMetadata',
|
|
||||||
'getSeries',
|
|
||||||
'unsubscribe',
|
|
||||||
'makeDatum'
|
|
||||||
]);
|
|
||||||
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
|
||||||
mockTelemetryHandle.request.andReturn(promise(undefined));
|
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([]);
|
|
||||||
mockTelemetryHandle.getMetadata.andReturn([]);
|
|
||||||
|
|
||||||
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
|
||||||
'handle'
|
|
||||||
]);
|
|
||||||
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
|
|
||||||
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"timeSystem",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
|
|
||||||
controller = new TableController(mockScope, mockTelemetryHandler,
|
|
||||||
mockTelemetryFormatter, mockAngularTimeout, {conductor: mockConductor});
|
|
||||||
|
|
||||||
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('makes use of the table', function () {
|
|
||||||
|
|
||||||
it('to create column definitions from telemetry' +
|
|
||||||
' metadata', function () {
|
|
||||||
controller.setup();
|
|
||||||
expect(mockTable.populateColumns).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('to create column configuration, which is written to the' +
|
|
||||||
' object model', function () {
|
|
||||||
controller.setup();
|
|
||||||
expect(mockTable.buildColumnConfiguration).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);
|
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
|
|
||||||
mockTelemetryHandle.getSeries.andReturn(mockSeries);
|
|
||||||
|
|
||||||
controller.addHistoricalData(mockDomainObject, mockSeries);
|
|
||||||
|
|
||||||
// Angular timeout is called a minumum of twice, regardless
|
|
||||||
// of batch size used.
|
|
||||||
expect(mockAngularTimeout).toHaveBeenCalled();
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
expect(mockAngularTimeout.calls.length).toEqual(2);
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
|
|
||||||
expect(controller.$scope.rows.length).toBe(5);
|
|
||||||
expect(controller.$scope.rows[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();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
describe('After populating columns', function () {
|
|
||||||
var metadata;
|
|
||||||
beforeEach(function () {
|
|
||||||
metadata = [{domains: [{name: 'time domain 1'}, {name: 'time domain 2'}]}, {domains: [{name: 'time domain 3'}, {name: 'time domain 4'}]}];
|
|
||||||
controller.populateColumns(metadata);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Automatically identifies time columns', function () {
|
|
||||||
expect(controller.timeColumns.length).toBe(4);
|
|
||||||
expect(controller.timeColumns[0]).toBe('time domain 1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Automatically sorts by time column that matches current' +
|
|
||||||
' time system', function () {
|
|
||||||
var key = 'time_domain_1',
|
|
||||||
name = 'time domain 1',
|
|
||||||
mockTimeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: key
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTable.columns = [
|
|
||||||
{
|
|
||||||
domainMetadata: {
|
|
||||||
key: key
|
|
||||||
},
|
|
||||||
getTitle: function () {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
domainMetadata: {
|
|
||||||
key: 'anotherColumn'
|
|
||||||
},
|
|
||||||
getTitle: function () {
|
|
||||||
return 'some other column';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
domainMetadata: {
|
|
||||||
key: 'thirdColumn'
|
|
||||||
},
|
|
||||||
getTitle: function () {
|
|
||||||
return 'a third column';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith('timeSystem', jasmine.any(Function));
|
|
||||||
getCallback(mockConductor.on, 'timeSystem')(mockTimeSystem);
|
|
||||||
expect(controller.$scope.defaultSort).toBe(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('Yields thread', function () {
|
|
||||||
var mockSeries,
|
|
||||||
mockRow;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockSeries = {
|
|
||||||
getPointCount: function () {
|
|
||||||
return 5;
|
|
||||||
},
|
|
||||||
getDomainValue: function () {
|
|
||||||
return 'Domain Value';
|
|
||||||
},
|
|
||||||
getRangeValue: function () {
|
|
||||||
return 'Range Value';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockRow = {'domain': 'Domain Value', 'range': 'Range Value'};
|
|
||||||
|
|
||||||
mockTelemetryHandle.makeDatum.andCallFake(function () {
|
|
||||||
return mockRow;
|
|
||||||
});
|
|
||||||
mockTable.getRowValues.andReturn(mockRow);
|
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
|
|
||||||
mockTelemetryHandle.getSeries.andReturn(mockSeries);
|
|
||||||
});
|
|
||||||
it('when row count exceeds batch size', function () {
|
|
||||||
controller.batchSize = 3;
|
|
||||||
controller.addHistoricalData(mockDomainObject, mockSeries);
|
|
||||||
|
|
||||||
//Timeout is called a minimum of two times
|
|
||||||
expect(mockAngularTimeout).toHaveBeenCalled();
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
|
|
||||||
expect(mockAngularTimeout.calls.length).toEqual(2);
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
|
|
||||||
//Because it yields, timeout will have been called a
|
|
||||||
// third time for the batch.
|
|
||||||
expect(mockAngularTimeout.calls.length).toEqual(3);
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
|
|
||||||
expect(controller.$scope.rows.length).toBe(5);
|
|
||||||
expect(controller.$scope.rows[0]).toBe(mockRow);
|
|
||||||
});
|
|
||||||
it('cancelling any outstanding timeouts', function () {
|
|
||||||
controller.batchSize = 3;
|
|
||||||
controller.addHistoricalData(mockDomainObject, mockSeries);
|
|
||||||
|
|
||||||
expect(mockAngularTimeout).toHaveBeenCalled();
|
|
||||||
mockAngularTimeout.mostRecentCall.args[0]();
|
|
||||||
|
|
||||||
controller.addHistoricalData(mockDomainObject, mockSeries);
|
|
||||||
|
|
||||||
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
|
|
||||||
});
|
|
||||||
it('cancels timeout on scope destruction', function () {
|
|
||||||
controller.batchSize = 3;
|
|
||||||
controller.addHistoricalData(mockDomainObject, mockSeries);
|
|
||||||
|
|
||||||
//Destroy is used by parent class as well, so multiple
|
|
||||||
// calls are made to scope.$on
|
|
||||||
var destroyCalls = mockScope.$on.calls.filter(function (call) {
|
|
||||||
return call.args[0] === '$destroy';
|
|
||||||
});
|
|
||||||
//Call destroy function
|
|
||||||
expect(destroyCalls.length).toEqual(2);
|
|
||||||
|
|
||||||
destroyCalls[0].args[1]();
|
|
||||||
expect(mockAngularTimeout.cancel).toHaveBeenCalledWith(mockTimeoutHandle);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -39,21 +39,13 @@ define(
|
|||||||
var controller,
|
var controller,
|
||||||
mockScope,
|
mockScope,
|
||||||
watches,
|
watches,
|
||||||
mockTimeout,
|
mockWindow,
|
||||||
mockElement,
|
mockElement,
|
||||||
mockExportService,
|
mockExportService,
|
||||||
mockConductor,
|
mockConductor,
|
||||||
mockFormatService,
|
mockFormatService,
|
||||||
mockFormat;
|
mockFormat;
|
||||||
|
|
||||||
function promise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return promise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCallback(target, event) {
|
function getCallback(target, event) {
|
||||||
return target.calls.filter(function (call) {
|
return target.calls.filter(function (call) {
|
||||||
return call.args[0] === event;
|
return call.args[0] === event;
|
||||||
@ -66,7 +58,8 @@ define(
|
|||||||
mockScope = jasmine.createSpyObj('scope', [
|
mockScope = jasmine.createSpyObj('scope', [
|
||||||
'$watch',
|
'$watch',
|
||||||
'$on',
|
'$on',
|
||||||
'$watchCollection'
|
'$watchCollection',
|
||||||
|
'$digest'
|
||||||
]);
|
]);
|
||||||
mockScope.$watchCollection.andCallFake(function (event, callback) {
|
mockScope.$watchCollection.andCallFake(function (event, callback) {
|
||||||
watches[event] = callback;
|
watches[event] = callback;
|
||||||
@ -86,8 +79,11 @@ define(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
mockScope.displayHeaders = true;
|
mockScope.displayHeaders = true;
|
||||||
mockTimeout = jasmine.createSpy('$timeout');
|
mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
|
||||||
mockTimeout.andReturn(promise(undefined));
|
mockWindow.requestAnimationFrame.andCallFake(function (f) {
|
||||||
|
return f();
|
||||||
|
});
|
||||||
|
|
||||||
mockFormat = jasmine.createSpyObj('formatter', [
|
mockFormat = jasmine.createSpyObj('formatter', [
|
||||||
'parse',
|
'parse',
|
||||||
'format'
|
'format'
|
||||||
@ -99,7 +95,7 @@ define(
|
|||||||
|
|
||||||
controller = new MCTTableController(
|
controller = new MCTTableController(
|
||||||
mockScope,
|
mockScope,
|
||||||
mockTimeout,
|
mockWindow,
|
||||||
mockElement,
|
mockElement,
|
||||||
mockExportService,
|
mockExportService,
|
||||||
mockFormatService,
|
mockFormatService,
|
||||||
@ -114,12 +110,12 @@ define(
|
|||||||
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
|
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('destroys listeners on destruction', function () {
|
it('unregisters listeners on destruction', function () {
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', controller.destroyConductorListeners);
|
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||||
getCallback(mockScope.$on, '$destroy')();
|
getCallback(mockScope.$on, '$destroy')();
|
||||||
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
|
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.setTimeOfInterest);
|
expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest);
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
|
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -233,12 +229,23 @@ define(
|
|||||||
|
|
||||||
//Mock setting the rows on scope
|
//Mock setting the rows on scope
|
||||||
var rowsCallback = getCallback(mockScope.$watch, 'rows');
|
var rowsCallback = getCallback(mockScope.$watch, 'rows');
|
||||||
rowsCallback(rowsAsc);
|
var setRowsPromise = rowsCallback(rowsAsc);
|
||||||
|
var promiseResolved = false;
|
||||||
|
setRowsPromise.then(function () {
|
||||||
|
promiseResolved = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
return promiseResolved;
|
||||||
|
}, "promise to resolve", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
expect(mockScope.toiRowIndex).toBe(2);
|
expect(mockScope.toiRowIndex).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rows', function () {
|
describe('rows', function () {
|
||||||
@ -287,7 +294,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Supports adding rows individually', function () {
|
it('Supports adding rows individually', function () {
|
||||||
var addRowFunc = getCallback(mockScope.$on, 'add:row'),
|
var addRowFunc = getCallback(mockScope.$on, 'add:rows'),
|
||||||
row4 = {
|
row4 = {
|
||||||
'col1': {'text': 'row3 col1'},
|
'col1': {'text': 'row3 col1'},
|
||||||
'col2': {'text': 'ghi'},
|
'col2': {'text': 'ghi'},
|
||||||
@ -296,15 +303,15 @@ define(
|
|||||||
controller.setRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
testRows.push(row4);
|
testRows.push(row4);
|
||||||
addRowFunc(undefined, 3);
|
addRowFunc(undefined, [row4]);
|
||||||
expect(mockScope.displayRows.length).toBe(4);
|
expect(mockScope.displayRows.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Supports removing rows individually', function () {
|
it('Supports removing rows individually', function () {
|
||||||
var removeRowFunc = getCallback(mockScope.$on, 'remove:row');
|
var removeRowFunc = getCallback(mockScope.$on, 'remove:rows');
|
||||||
controller.setRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
removeRowFunc(undefined, 2);
|
removeRowFunc(undefined, [testRows[2]]);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
expect(controller.setVisibleRows).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -366,7 +373,7 @@ define(
|
|||||||
it('Allows sort column to be changed externally by ' +
|
it('Allows sort column to be changed externally by ' +
|
||||||
'setting or changing sortBy attribute', function () {
|
'setting or changing sortBy attribute', function () {
|
||||||
mockScope.displayRows = testRows;
|
mockScope.displayRows = testRows;
|
||||||
var sortByCB = getCallback(mockScope.$watch, 'sortColumn');
|
var sortByCB = getCallback(mockScope.$watch, 'defaultSort');
|
||||||
sortByCB('col2');
|
sortByCB('col2');
|
||||||
|
|
||||||
expect(mockScope.sortDirection).toEqual('asc');
|
expect(mockScope.sortDirection).toEqual('asc');
|
||||||
@ -381,11 +388,22 @@ define(
|
|||||||
it('updates visible rows in scope', function () {
|
it('updates visible rows in scope', function () {
|
||||||
var oldRows;
|
var oldRows;
|
||||||
mockScope.rows = testRows;
|
mockScope.rows = testRows;
|
||||||
controller.setRows(testRows);
|
var setRowsPromise = controller.setRows(testRows);
|
||||||
|
var promiseResolved = false;
|
||||||
|
setRowsPromise.then(function () {
|
||||||
|
promiseResolved = true;
|
||||||
|
});
|
||||||
oldRows = mockScope.visibleRows;
|
oldRows = mockScope.visibleRows;
|
||||||
mockScope.toggleSort('col2');
|
mockScope.toggleSort('col2');
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
return promiseResolved;
|
||||||
|
}, "promise to resolve", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
expect(mockScope.visibleRows).not.toEqual(oldRows);
|
expect(mockScope.visibleRows).not.toEqual(oldRows);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('correctly sorts rows of differing types', function () {
|
it('correctly sorts rows of differing types', function () {
|
||||||
mockScope.sortColumn = 'col2';
|
mockScope.sortColumn = 'col2';
|
||||||
@ -464,21 +482,10 @@ define(
|
|||||||
|
|
||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
|
|
||||||
mockScope.rows.push(row4);
|
controller.addRows(undefined, [row4, row5, row6, row6]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||||
|
expect(mockScope.displayRows[6].col2.text).toEqual('aaa');
|
||||||
mockScope.rows.push(row5);
|
//Added a duplicate row
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
|
||||||
|
|
||||||
//Add a duplicate row
|
|
||||||
mockScope.rows.push(row6);
|
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
@ -493,13 +500,11 @@ define(
|
|||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
mockScope.displayRows = controller.filterRows(testRows);
|
mockScope.displayRows = controller.filterRows(testRows);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
controller.addRows(undefined, [row5]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
controller.addRows(undefined, [row6]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
//Row was not added because does not match filter
|
//Row was not added because does not match filter
|
||||||
});
|
});
|
||||||
@ -512,12 +517,10 @@ define(
|
|||||||
|
|
||||||
mockScope.displayRows = testRows.slice(0);
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
controller.addRows(undefined, [row5]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
controller.addRows(undefined, [row6]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -535,8 +538,7 @@ define(
|
|||||||
|
|
||||||
mockScope.displayRows = testRows.slice(0);
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
mockScope.rows.push(row7);
|
controller.addRows(undefined, [row7]);
|
||||||
controller.addRows(undefined, mockScope.rows.length - 1);
|
|
||||||
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../../src/controllers/RealtimeTableController"
|
|
||||||
],
|
|
||||||
function (TableController) {
|
|
||||||
|
|
||||||
describe('The real-time table controller', function () {
|
|
||||||
var mockScope,
|
|
||||||
mockTelemetryHandler,
|
|
||||||
mockTelemetryHandle,
|
|
||||||
mockTelemetryFormatter,
|
|
||||||
mockDomainObject,
|
|
||||||
mockTable,
|
|
||||||
mockConfiguration,
|
|
||||||
watches,
|
|
||||||
mockTableRow,
|
|
||||||
mockConductor,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
function promise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return promise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
watches = {};
|
|
||||||
mockTableRow = {'col1': 'val1', 'col2': 'row2'};
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
|
||||||
'$on',
|
|
||||||
'$watch',
|
|
||||||
'$watchCollection',
|
|
||||||
'$digest',
|
|
||||||
'$broadcast'
|
|
||||||
]);
|
|
||||||
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',
|
|
||||||
[
|
|
||||||
'populateColumns',
|
|
||||||
'buildColumnConfiguration',
|
|
||||||
'getRowValues',
|
|
||||||
'saveColumnConfiguration'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTable.columns = [];
|
|
||||||
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
|
||||||
mockTable.getRowValues.andReturn(mockTableRow);
|
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
|
||||||
'getCapability',
|
|
||||||
'useCapability',
|
|
||||||
'getModel'
|
|
||||||
]);
|
|
||||||
mockDomainObject.getModel.andReturn({});
|
|
||||||
mockDomainObject.getCapability.andReturn(
|
|
||||||
{
|
|
||||||
getMetadata: function () {
|
|
||||||
return {ranges: [{format: 'string'}]};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
|
|
||||||
'getMetadata',
|
|
||||||
'unsubscribe',
|
|
||||||
'getDatum',
|
|
||||||
'promiseTelemetryObjects',
|
|
||||||
'getTelemetryObjects',
|
|
||||||
'request',
|
|
||||||
'getMetadata'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Arbitrary array with non-zero length, contents are not
|
|
||||||
// used by mocks
|
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
|
||||||
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
|
||||||
mockTelemetryHandle.getDatum.andReturn({});
|
|
||||||
mockTelemetryHandle.request.andReturn(promise(undefined));
|
|
||||||
mockTelemetryHandle.getMetadata.andReturn([]);
|
|
||||||
|
|
||||||
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
|
||||||
'handle'
|
|
||||||
]);
|
|
||||||
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
|
|
||||||
|
|
||||||
mockConductor = jasmine.createSpyObj('conductor', [
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'bounds',
|
|
||||||
'timeSystem',
|
|
||||||
'timeOfInterest'
|
|
||||||
]);
|
|
||||||
|
|
||||||
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter, {conductor: mockConductor});
|
|
||||||
controller.table = mockTable;
|
|
||||||
controller.handle = mockTelemetryHandle;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('registers for streaming telemetry', function () {
|
|
||||||
controller.subscribe();
|
|
||||||
expect(mockTelemetryHandler.handle).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('receives new telemetry', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
controller.subscribe();
|
|
||||||
mockScope.rows = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates table with new streaming telemetry', function () {
|
|
||||||
mockTelemetryHandler.handle.mostRecentCall.args[1]();
|
|
||||||
expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0);
|
|
||||||
});
|
|
||||||
it('observes the row limit', function () {
|
|
||||||
var i = 0;
|
|
||||||
controller.maxRows = 10;
|
|
||||||
|
|
||||||
//Fill rows array with elements
|
|
||||||
for (; i < 10; i++) {
|
|
||||||
mockScope.rows.push({row: i});
|
|
||||||
}
|
|
||||||
mockTelemetryHandler.handle.mostRecentCall.args[1]();
|
|
||||||
expect(mockScope.rows.length).toBe(controller.maxRows);
|
|
||||||
expect(mockScope.rows[mockScope.rows.length - 1]).toBe(mockTableRow);
|
|
||||||
expect(mockScope.rows[0].row).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -0,0 +1,364 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'../../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.filter(function (call) {
|
||||||
|
return call.args[0] === event;
|
||||||
|
})[0].args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockBounds = {
|
||||||
|
start: 0,
|
||||||
|
end: 10
|
||||||
|
};
|
||||||
|
mockConductor = jasmine.createSpyObj("conductor", [
|
||||||
|
"bounds",
|
||||||
|
"follow",
|
||||||
|
"on",
|
||||||
|
"off",
|
||||||
|
"timeSystem"
|
||||||
|
]);
|
||||||
|
mockConductor.bounds.andReturn(mockBounds);
|
||||||
|
mockConductor.follow.andReturn(false);
|
||||||
|
|
||||||
|
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||||
|
"getModel",
|
||||||
|
"getId",
|
||||||
|
"useCapability"
|
||||||
|
]);
|
||||||
|
mockDomainObject.getModel.andReturn({});
|
||||||
|
mockDomainObject.getId.andReturn("mockId");
|
||||||
|
mockDomainObject.useCapability.andReturn(true);
|
||||||
|
|
||||||
|
mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [
|
||||||
|
"get"
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockObjectAPI = jasmine.createSpyObj("objectAPI", [
|
||||||
|
"observe"
|
||||||
|
]);
|
||||||
|
unobserve = jasmine.createSpy("unobserve");
|
||||||
|
mockObjectAPI.observe.andReturn(unobserve);
|
||||||
|
|
||||||
|
mockScope = jasmine.createSpyObj("scope", [
|
||||||
|
"$on",
|
||||||
|
"$watch",
|
||||||
|
"$broadcast"
|
||||||
|
]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
|
mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
|
||||||
|
"canProvideTelemetry",
|
||||||
|
"subscribe",
|
||||||
|
"getMetadata",
|
||||||
|
"commonValuesForHints",
|
||||||
|
"request",
|
||||||
|
"limitEvaluator",
|
||||||
|
"getValueFormatter"
|
||||||
|
]);
|
||||||
|
mockTelemetryAPI.commonValuesForHints.andReturn([]);
|
||||||
|
mockTelemetryAPI.request.andReturn(Promise.resolve([]));
|
||||||
|
|
||||||
|
|
||||||
|
mockTelemetryAPI.canProvideTelemetry.andReturn(false);
|
||||||
|
|
||||||
|
mockTimeout = jasmine.createSpy("timeout");
|
||||||
|
mockTimeout.andReturn(1); // Return something
|
||||||
|
mockTimeout.cancel = jasmine.createSpy("cancel");
|
||||||
|
|
||||||
|
mockAPI = {
|
||||||
|
conductor: 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.mostRecentCall.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("follow", jasmine.any(Function));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deregisters all listeners on scope destruction', function () {
|
||||||
|
var timeSystemListener,
|
||||||
|
boundsListener,
|
||||||
|
followListener;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
controller.registerChangeListeners();
|
||||||
|
|
||||||
|
timeSystemListener = getCallback(mockConductor.on, "timeSystem");
|
||||||
|
boundsListener = getCallback(mockConductor.on, "bounds");
|
||||||
|
followListener = getCallback(mockConductor.on, "follow");
|
||||||
|
|
||||||
|
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("follow", followListener);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe ('Subscribes to new data', function () {
|
||||||
|
var mockComposition,
|
||||||
|
mockTelemetryObject,
|
||||||
|
mockChildren,
|
||||||
|
unsubscribe,
|
||||||
|
done;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockComposition = jasmine.createSpyObj("composition", [
|
||||||
|
"load"
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockTelemetryObject = jasmine.createSpyObj("mockTelemetryObject", [
|
||||||
|
"something"
|
||||||
|
]);
|
||||||
|
mockTelemetryObject.identifier = {
|
||||||
|
key: "mockTelemetryObject"
|
||||||
|
};
|
||||||
|
|
||||||
|
unsubscribe = jasmine.createSpy("unsubscribe");
|
||||||
|
mockTelemetryAPI.subscribe.andReturn(unsubscribe);
|
||||||
|
|
||||||
|
mockChildren = [mockTelemetryObject];
|
||||||
|
mockComposition.load.andReturn(Promise.resolve(mockChildren));
|
||||||
|
mockCompositionAPI.get.andReturn(mockComposition);
|
||||||
|
|
||||||
|
mockTelemetryAPI.canProvideTelemetry.andCallFake(function (obj) {
|
||||||
|
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
||||||
|
});
|
||||||
|
|
||||||
|
done = false;
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches historical data', function () {
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes to new data', function () {
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
it('and unsubscribes on view destruction', function () {
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
var destroy = getCallback(mockScope.$on, "$destroy");
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('When in real-time mode, enables auto-scroll', function () {
|
||||||
|
controller.registerChangeListeners();
|
||||||
|
|
||||||
|
var followCallback = getCallback(mockConductor.on, "follow");
|
||||||
|
//Confirm pre-condition
|
||||||
|
expect(mockScope.autoScroll).toBeFalsy();
|
||||||
|
|
||||||
|
//Mock setting the conductor to 'follow' mode
|
||||||
|
followCallback(true);
|
||||||
|
expect(mockScope.autoScroll).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('populates table columns', function () {
|
||||||
|
var domainMetadata;
|
||||||
|
var allMetadata;
|
||||||
|
var mockTimeSystem;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
domainMetadata = [{
|
||||||
|
key: "column1",
|
||||||
|
name: "Column 1",
|
||||||
|
hints: {}
|
||||||
|
}];
|
||||||
|
|
||||||
|
allMetadata = [{
|
||||||
|
key: "column1",
|
||||||
|
name: "Column 1",
|
||||||
|
hints: {}
|
||||||
|
}, {
|
||||||
|
key: "column2",
|
||||||
|
name: "Column 2",
|
||||||
|
hints: {}
|
||||||
|
}, {
|
||||||
|
key: "column3",
|
||||||
|
name: "Column 3",
|
||||||
|
hints: {}
|
||||||
|
}];
|
||||||
|
|
||||||
|
mockTimeSystem = {
|
||||||
|
metadata: {
|
||||||
|
key: "column1"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockTelemetryAPI.commonValuesForHints.andCallFake(function (metadata, hints) {
|
||||||
|
if (_.eq(hints, ["x"])) {
|
||||||
|
return domainMetadata;
|
||||||
|
} else if (_.eq(hints, [])) {
|
||||||
|
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).not.toEqual("Column 1");
|
||||||
|
controller.sortByTimeSystem(mockTimeSystem);
|
||||||
|
expect(mockScope.defaultSort).toEqual("Column 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.andReturn(Promise.resolve(mockHistoricalData));
|
||||||
|
controller.getHistoricalData([mockDomainObject]);
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
return !!controller.timeoutHandle;
|
||||||
|
}, "first batch to be processed", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
//Verify that timeout is being used to yield process
|
||||||
|
expect(mockTimeout).toHaveBeenCalled();
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
expect(mockTimeout.calls.length).toBe(2);
|
||||||
|
mockTimeout.mostRecentCall.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").andCallThrough();
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user