mirror of
https://github.com/nasa/openmct.git
synced 2025-02-07 03:29:24 +00:00
[Tables] Refactoring for consolidation of historical and real-time tables
Added batch processing of large historical queries. #1077
This commit is contained in:
parent
3544caf4be
commit
2a4944d6ee
@ -217,8 +217,8 @@ define(
|
|||||||
if (handle) {
|
if (handle) {
|
||||||
handle.unsubscribe();
|
handle.unsubscribe();
|
||||||
handle = undefined;
|
handle = undefined;
|
||||||
conductor.off("timeOfInterest", changeTimeOfInterest);
|
|
||||||
}
|
}
|
||||||
|
conductor.off("timeOfInterest", changeTimeOfInterest);
|
||||||
}
|
}
|
||||||
|
|
||||||
function requery() {
|
function requery() {
|
||||||
|
@ -87,7 +87,7 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "TelemetryTableController",
|
"key": "TelemetryTableController",
|
||||||
"implementation": TelemetryTableController,
|
"implementation": TelemetryTableController,
|
||||||
"depends": ["$scope", "openmct"]
|
"depends": ["$scope", "$timeout", "openmct"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "TableOptionsController",
|
"key": "TableOptionsController",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
Export
|
Export
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
|
<div class="l-view-section scrolling" style="overflow: auto;">
|
||||||
<table class="sizing-table">
|
<table class="sizing-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -32,8 +32,7 @@
|
|||||||
enableSort ? 'sortable' : '',
|
enableSort ? 'sortable' : '',
|
||||||
sortColumn === header ? 'sort' : '',
|
sortColumn === header ? 'sort' : '',
|
||||||
sortDirection || ''
|
sortDirection || ''
|
||||||
].join(' ')"
|
].join(' ')">
|
||||||
ng-click="toggleSort(header)">
|
|
||||||
{{ header }}
|
{{ header }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -59,8 +58,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-repeat-end
|
<tr ng-repeat-end
|
||||||
ng-style="{ top: visibleRow.offsetY + 'px' }"
|
ng-style="{ top: visibleRow.offsetY + 'px' }">
|
||||||
ng-click="table.onRowClick($event, visibleRow.rowIndex) ">
|
|
||||||
<td ng-repeat="header in displayHeaders"
|
<td ng-repeat="header in displayHeaders"
|
||||||
ng-style=" {
|
ng-style=" {
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
rows="rows"
|
rows="rows"
|
||||||
enableFilter="true"
|
enableFilter="true"
|
||||||
enableSort="true"
|
enableSort="true"
|
||||||
sort-column="defaultSort"
|
default-sort="defaultSort"
|
||||||
class="tabular-holder has-control-bar">
|
class="tabular-holder has-control-bar">
|
||||||
</mct-table>
|
</mct-table>
|
||||||
</div>
|
</div>
|
@ -53,6 +53,7 @@ define(
|
|||||||
var formatter = telemetryApi.getValueFormatter(metadatum);
|
var formatter = telemetryApi.getValueFormatter(metadatum);
|
||||||
|
|
||||||
self.addColumn({
|
self.addColumn({
|
||||||
|
metadata: metadatum,
|
||||||
getTitle: function () {
|
getTitle: function () {
|
||||||
return metadatum.name;
|
return metadatum.name;
|
||||||
},
|
},
|
||||||
|
@ -12,12 +12,12 @@ define(
|
|||||||
* @param element
|
* @param element
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function MCTTableController($scope, $timeout, element, exportService, formatService, openmct) {
|
function MCTTableController($scope, $window, element, exportService, formatService, openmct) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.element = $(element[0]);
|
this.element = $(element[0]);
|
||||||
this.$timeout = $timeout;
|
this.$window = $window;
|
||||||
this.maxDisplayRows = 50;
|
this.maxDisplayRows = 50;
|
||||||
|
|
||||||
this.scrollable = this.element.find('.l-view-section.scrolling').first();
|
this.scrollable = this.element.find('.l-view-section.scrolling').first();
|
||||||
@ -27,15 +27,39 @@ define(
|
|||||||
this.conductor = openmct.conductor;
|
this.conductor = openmct.conductor;
|
||||||
this.toiFormatter = undefined;
|
this.toiFormatter = undefined;
|
||||||
this.formatService = formatService;
|
this.formatService = formatService;
|
||||||
|
this.callbacks = {};
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
//Bind all class functions to 'this'
|
||||||
Object.keys(MCTTableController.prototype).filter(function (key) {
|
_.bindAll(this, [
|
||||||
return typeof MCTTableController.prototype[key] === 'function';
|
'destroyConductorListeners',
|
||||||
}).forEach(function (key) {
|
'changeTimeSystem',
|
||||||
this[key] = MCTTableController.prototype[key].bind(this);
|
'scrollToBottom',
|
||||||
}.bind(this));
|
'addRow',
|
||||||
|
'removeRow',
|
||||||
|
'onScroll',
|
||||||
|
'firstVisible',
|
||||||
|
'lastVisible',
|
||||||
|
'setVisibleRows',
|
||||||
|
'setHeaders',
|
||||||
|
'setElementSizes',
|
||||||
|
'binarySearch',
|
||||||
|
'insertSorted',
|
||||||
|
'sortComparator',
|
||||||
|
'sortRows',
|
||||||
|
'buildLargestRow',
|
||||||
|
'resize',
|
||||||
|
'filterAndSort',
|
||||||
|
'setRows',
|
||||||
|
'filterRows',
|
||||||
|
'scrollToRow',
|
||||||
|
'setTimeOfInterestRow',
|
||||||
|
'changeTimeOfInterest',
|
||||||
|
'changeBounds',
|
||||||
|
'onRowClick',
|
||||||
|
'digest'
|
||||||
|
]);
|
||||||
|
|
||||||
this.scrollable.on('scroll', this.onScroll.bind(this));
|
this.scrollable.on('scroll', this.onScroll);
|
||||||
|
|
||||||
$scope.visibleRows = [];
|
$scope.visibleRows = [];
|
||||||
|
|
||||||
@ -86,7 +110,7 @@ define(
|
|||||||
$scope.sortDirection = 'asc';
|
$scope.sortDirection = 'asc';
|
||||||
}
|
}
|
||||||
self.setRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
self.setTimeOfInterest(self.conductor.timeOfInterest());
|
self.setTimeOfInterestRow(self.conductor.timeOfInterest());
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -108,7 +132,11 @@ define(
|
|||||||
* Populated from the default-sort attribute on MctTable
|
* Populated from the default-sort attribute on MctTable
|
||||||
* directive tag.
|
* directive tag.
|
||||||
*/
|
*/
|
||||||
$scope.$watch('sortColumn', $scope.toggleSort);
|
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
|
||||||
|
if (newColumn !== oldColumn) {
|
||||||
|
$scope.toggleSort(newColumn)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Listen for resize events to trigger recalculation of table width
|
* Listen for resize events to trigger recalculation of table width
|
||||||
@ -125,7 +153,7 @@ define(
|
|||||||
this.destroyConductorListeners();
|
this.destroyConductorListeners();
|
||||||
|
|
||||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||||
this.conductor.on('timeOfInterest', this.setTimeOfInterest);
|
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.on('bounds', this.changeBounds);
|
this.conductor.on('bounds', this.changeBounds);
|
||||||
|
|
||||||
// If time system defined, set initially
|
// If time system defined, set initially
|
||||||
@ -135,12 +163,22 @@ define(
|
|||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroyConductorListeners);
|
console.log('constructed');
|
||||||
}
|
|
||||||
|
$scope.$on('$destroy', function() {
|
||||||
|
this.scrollable.off('scroll', this.onScroll);
|
||||||
|
this.destroyConductorListeners();
|
||||||
|
|
||||||
|
// In case for some reason this controller instance lingers around,
|
||||||
|
// destroy scope as it can be extremely large for large tables.
|
||||||
|
delete this.$scope;
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
MCTTableController.prototype.destroyConductorListeners = function () {
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||||
this.conductor.off('timeOfInterest', this.setTimeOfInterest);
|
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.off('bounds', this.changeBounds);
|
this.conductor.off('bounds', this.changeBounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +198,7 @@ define(
|
|||||||
//Use timeout to defer execution until next digest when any
|
//Use timeout to defer execution until next digest when any
|
||||||
// pending UI changes have completed, eg. a new row in the table.
|
// pending UI changes have completed, eg. a new row in the table.
|
||||||
if (this.$scope.autoScroll) {
|
if (this.$scope.autoScroll) {
|
||||||
this.$timeout(function () {
|
this.digest(function () {
|
||||||
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
|
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -183,6 +221,12 @@ define(
|
|||||||
this.resize([this.$scope.sizingRow, row])
|
this.resize([this.$scope.sizingRow, row])
|
||||||
.then(this.setVisibleRows.bind(this))
|
.then(this.setVisibleRows.bind(this))
|
||||||
.then(this.scrollToBottom.bind(this));
|
.then(this.scrollToBottom.bind(this));
|
||||||
|
|
||||||
|
var toi = this.conductor.timeOfInterest();
|
||||||
|
if (toi !== -1) {
|
||||||
|
this.setTimeOfInterestRow(toi);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,8 +237,8 @@ define(
|
|||||||
*/
|
*/
|
||||||
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
||||||
var row = this.$scope.rows[rowIndex],
|
var row = this.$scope.rows[rowIndex],
|
||||||
// Do a sequential search here. Only way of finding row is by
|
// Do a sequential search here. Only way of finding row is by
|
||||||
// object equality, so array is in effect unsorted.
|
// object equality, so array is in effect unsorted.
|
||||||
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
||||||
if (indexInDisplayRows !== -1) {
|
if (indexInDisplayRows !== -1) {
|
||||||
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
||||||
@ -522,6 +566,27 @@ define(
|
|||||||
return largestRow;
|
return largestRow;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCTTableController.prototype.digest = function (callback) {
|
||||||
|
var scope = this.$scope;
|
||||||
|
var callbacks = this.callbacks;
|
||||||
|
var requestAnimationFrame = this.$window.requestAnimationFrame;
|
||||||
|
|
||||||
|
var promise = callbacks[callback];
|
||||||
|
|
||||||
|
if (!promise){
|
||||||
|
promise = new Promise(function (resolve) {
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
scope.$digest();
|
||||||
|
delete callbacks[callback];
|
||||||
|
resolve(callback && callback());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
callbacks[callback] = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the widest row in the table, and if necessary, resizes
|
* Calculates the widest row in the table, and if necessary, resizes
|
||||||
* the table accordingly
|
* the table accordingly
|
||||||
@ -533,7 +598,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
MCTTableController.prototype.resize = function (rows) {
|
MCTTableController.prototype.resize = function (rows) {
|
||||||
this.$scope.sizingRow = this.buildLargestRow(rows);
|
this.$scope.sizingRow = this.buildLargestRow(rows);
|
||||||
return this.$timeout(this.setElementSizes.bind(this));
|
return this.digest(this.setElementSizes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -566,15 +631,15 @@ define(
|
|||||||
.then(this.setVisibleRows)
|
.then(this.setVisibleRows)
|
||||||
//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(this.$timeout)
|
.then(this.digest)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
//If TOI specified, scroll to it
|
//If TOI specified, scroll to it
|
||||||
var timeOfInterest = this.conductor.timeOfInterest();
|
var timeOfInterest = this.conductor.timeOfInterest();
|
||||||
if (timeOfInterest) {
|
if (timeOfInterest) {
|
||||||
this.setTimeOfInterest(timeOfInterest);
|
this.setTimeOfInterestRow(timeOfInterest);
|
||||||
|
this.scrollToRow(this.$scope.toiRowIndex);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -635,7 +700,7 @@ define(
|
|||||||
* Update rows with new data. If filtering is enabled, rows
|
* Update rows with new data. If filtering is enabled, rows
|
||||||
* will be sorted before display.
|
* will be sorted before display.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.setTimeOfInterest = function (newTOI) {
|
MCTTableController.prototype.setTimeOfInterestRow = function (newTOI) {
|
||||||
var isSortedByTime =
|
var isSortedByTime =
|
||||||
this.$scope.timeColumns &&
|
this.$scope.timeColumns &&
|
||||||
this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
|
this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
|
||||||
@ -652,17 +717,22 @@ define(
|
|||||||
|
|
||||||
if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
|
if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
|
||||||
this.$scope.toiRowIndex = rowIndex;
|
this.$scope.toiRowIndex = rowIndex;
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MCTTableController.prototype.changeTimeOfInterest = function (newTOI) {
|
||||||
|
this.setTimeOfInterestRow(newTOI);
|
||||||
|
this.scrollToRow(this.$scope.toiRowIndex);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On zoom, pan, etc. reset TOI
|
* On zoom, pan, etc. reset TOI
|
||||||
* @param bounds
|
* @param bounds
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.changeBounds = function (bounds) {
|
MCTTableController.prototype.changeBounds = function (bounds) {
|
||||||
this.setTimeOfInterest(this.conductor.timeOfInterest());
|
this.setTimeOfInterestRow(this.conductor.timeOfInterest());
|
||||||
|
this.scrollToRow(this.$scope.toiRowIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,43 +42,47 @@ define(
|
|||||||
*/
|
*/
|
||||||
function TelemetryTableController(
|
function TelemetryTableController(
|
||||||
$scope,
|
$scope,
|
||||||
|
$timeout,
|
||||||
openmct
|
openmct
|
||||||
) {
|
) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
|
this.$timeout = $timeout;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.batchSize = 1000;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialization block
|
||||||
|
*/
|
||||||
this.columns = {}; //Range and Domain columns
|
this.columns = {}; //Range and Domain columns
|
||||||
this.handle = undefined;
|
this.deregisterListeners = [];
|
||||||
|
this.subscriptions = [];
|
||||||
|
this.timeColumns = [];
|
||||||
|
$scope.rows = [];
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
this.table = new TableConfiguration($scope.domainObject,
|
||||||
openmct);
|
openmct);
|
||||||
this.changeListeners = [];
|
this.lastBounds = this.openmct.conductor.bounds();
|
||||||
this.conductor = openmct.conductor;
|
this.requestTime = 0;
|
||||||
this.openmct = openmct;
|
|
||||||
this.newObject = objectUtils.toNewFormat($scope.domainObject.getModel(), $scope.domainObject.getId());
|
|
||||||
|
|
||||||
$scope.rows = [];
|
/*
|
||||||
|
* Create a new format object from legacy object, and replace it
|
||||||
|
* when it changes
|
||||||
|
*/
|
||||||
|
this.newObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
|
||||||
|
$scope.domainObject.getId());
|
||||||
|
|
||||||
// Subscribe to telemetry when a domain object becomes available
|
_.bindAll(this, [
|
||||||
this.$scope.$watch('domainObject', function () {
|
'destroy',
|
||||||
self.subscribe();
|
'sortByTimeSystem',
|
||||||
self.registerChangeListeners();
|
'loadColumns',
|
||||||
});
|
'getHistoricalData',
|
||||||
this.mutationListener = openmct.objects.observe(this.newObject, "*", function (domainObject){
|
'subscribeToNewData',
|
||||||
self.newObject = domainObject;
|
'changeBounds'
|
||||||
});
|
]);
|
||||||
|
|
||||||
this.destroy = this.destroy.bind(this);
|
this.getData();
|
||||||
|
this.registerChangeListeners();
|
||||||
|
|
||||||
// Unsubscribe when the plot is destroyed
|
|
||||||
this.$scope.$on("$destroy", this.destroy);
|
this.$scope.$on("$destroy", this.destroy);
|
||||||
this.timeColumns = [];
|
|
||||||
|
|
||||||
|
|
||||||
this.sortByTimeSystem = this.sortByTimeSystem.bind(this);
|
|
||||||
this.conductor.on('timeSystem', this.sortByTimeSystem);
|
|
||||||
this.conductor.off('timeSystem', this.sortByTimeSystem);
|
|
||||||
|
|
||||||
this.subscriptions = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,133 +95,254 @@ define(
|
|||||||
scope.defaultSort = undefined;
|
scope.defaultSort = undefined;
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.table.columns.forEach(function (column) {
|
this.table.columns.forEach(function (column) {
|
||||||
if (column.domainMetadata && column.domainMetadata.key === timeSystem.metadata.key) {
|
if (column.metadata.key === timeSystem.metadata.key) {
|
||||||
scope.defaultSort = column.getTitle();
|
scope.defaultSort = column.getTitle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.$scope.rows = _.sortBy(this.$scope.rows, function (row) {
|
||||||
|
return row[this.$scope.defaultSort];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TelemetryTableController.prototype.unregisterChangeListeners = function () {
|
|
||||||
this.changeListeners.forEach(function (listener) {
|
|
||||||
return listener && listener();
|
|
||||||
});
|
|
||||||
this.changeListeners = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defer registration of change listeners until domain object is
|
* Attach listeners to domain object to respond to changes due to
|
||||||
* available in order to avoid race conditions
|
* composition, etc.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||||
var self = this;
|
this.deregisterListeners.forEach(function (deregister){
|
||||||
this.unregisterChangeListeners();
|
deregister();
|
||||||
|
});
|
||||||
|
this.deregisterListeners = [];
|
||||||
|
|
||||||
// When composition changes, re-subscribe to the various
|
this.deregisterListeners.push(
|
||||||
// telemetry subscriptions
|
this.openmct.objects.observe(this.newObject, "*",
|
||||||
this.changeListeners.push(this.$scope.$watchCollection(
|
function (domainObject){
|
||||||
'domainObject.getModel().composition',
|
this.newObject = domainObject;
|
||||||
function (newVal, oldVal) {
|
this.getData();
|
||||||
if (newVal !== oldVal) {
|
}.bind(this)
|
||||||
self.subscribe();
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
|
||||||
|
this.openmct.conductor.on('bounds', this.changeBounds);
|
||||||
|
};
|
||||||
|
|
||||||
|
TelemetryTableController.prototype.tick = function (bounds) {
|
||||||
|
// Can't do ticking until we change how data is handled
|
||||||
|
// Pass raw values to table, with format function
|
||||||
|
|
||||||
|
/*if (this.$scope.defaultSort) {
|
||||||
|
this.$scope.rows.filter(function (row){
|
||||||
|
return row[]
|
||||||
|
})
|
||||||
|
}*/
|
||||||
|
};
|
||||||
|
|
||||||
|
TelemetryTableController.prototype.changeBounds = function (bounds) {
|
||||||
|
var follow = this.openmct.conductor.follow();
|
||||||
|
var isTick = follow &&
|
||||||
|
bounds.start !== this.lastBounds.start &&
|
||||||
|
bounds.end !== this.lastBounds.end;
|
||||||
|
var isDeltaChange = follow &&
|
||||||
|
!isTick &&
|
||||||
|
(bounds.start !== this.lastBounds.start ||
|
||||||
|
bounds.end !== this.lastBounds.end);
|
||||||
|
|
||||||
|
if (isTick){
|
||||||
|
// Treat it as a realtime tick
|
||||||
|
// Drop old data that falls outside of bounds
|
||||||
|
this.tick(bounds);
|
||||||
|
} else if (isDeltaChange){
|
||||||
|
// No idea...
|
||||||
|
// Historical query for bounds, then tick on
|
||||||
|
this.getData();
|
||||||
|
} else {
|
||||||
|
// Is fixed bounds change
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
this.lastBounds = bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the current subscription (called when scope is destroyed)
|
* Release the current subscription (called when scope is destroyed)
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.destroy = function () {
|
TelemetryTableController.prototype.destroy = function () {
|
||||||
|
|
||||||
|
this.openmct.conductor.off('timeSystem', this.sortByTimeSystem);
|
||||||
|
this.openmct.conductor.off('bounds', this.changeBounds);
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
subscription()
|
subscription();
|
||||||
});
|
});
|
||||||
this.mutationListener();
|
this.deregisterListeners.forEach(function (deregister){
|
||||||
|
deregister();
|
||||||
|
});
|
||||||
|
this.subscriptions = [];
|
||||||
|
this.deregisterListeners = [];
|
||||||
|
|
||||||
|
if (this.timeoutHandle) {
|
||||||
|
this.$timeout.cancel(this.timeoutHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case controller instance lingers around (currently there is a
|
||||||
|
// temporary memory leak with PlotController), clean up scope as it
|
||||||
|
// can be extremely large.
|
||||||
|
this.$scope = null;
|
||||||
|
this.table = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param objects
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.loadColumns = function (objects) {
|
||||||
|
var telemetryApi = this.openmct.telemetry;
|
||||||
|
|
||||||
|
if (objects.length > 0) {
|
||||||
|
var metadatas = objects.map(telemetryApi.getMetadata.bind(telemetryApi));
|
||||||
|
var allColumns = telemetryApi.commonValuesForHints(metadatas, []);
|
||||||
|
|
||||||
|
this.table.populateColumns(allColumns);
|
||||||
|
|
||||||
|
this.timeColumns = telemetryApi.commonValuesForHints(metadatas, ['x']).map(function (metadatum) {
|
||||||
|
return metadatum.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterColumns();
|
||||||
|
|
||||||
|
var timeSystem = this.openmct.conductor.timeSystem();
|
||||||
|
if (timeSystem) {
|
||||||
|
this.sortByTimeSystem(timeSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objects;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new subscription. This can be overridden by children to
|
* @private
|
||||||
change default behaviour (which is to retrieve historical telemetry
|
* @param objects The domain objects to request telemetry for
|
||||||
only).
|
* @returns {*|{configFile}|app|boolean|Route|Object}
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.subscribe = function () {
|
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
||||||
var self = this;
|
var openmct = this.openmct;
|
||||||
|
var bounds = openmct.conductor.bounds();
|
||||||
|
var scope = this.$scope;
|
||||||
|
var processedObjects = 0;
|
||||||
|
var requestTime = this.lastRequestTime = Date.now();
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject){
|
||||||
|
console.log('Created promise');
|
||||||
|
function finishProcessing(tableRows){
|
||||||
|
scope.rows = tableRows;
|
||||||
|
scope.loading = false;
|
||||||
|
console.log('Resolved promise');
|
||||||
|
resolve(tableRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processData(historicalData, index, rowData, limitEvaluator){
|
||||||
|
console.log("Processing batch");
|
||||||
|
if (index >= historicalData.length) {
|
||||||
|
processedObjects++;
|
||||||
|
|
||||||
|
if (processedObjects === objects.length) {
|
||||||
|
finishProcessing(rowData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rowData = rowData.concat(historicalData.slice(index, index + this.batchSize)
|
||||||
|
.map(this.table.getRowValues.bind(this.table, limitEvaluator)));
|
||||||
|
this.timeoutHandle = this.$timeout(processData.bind(
|
||||||
|
this,
|
||||||
|
historicalData,
|
||||||
|
index + this.batchSize,
|
||||||
|
rowData,
|
||||||
|
limitEvaluator
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTableRows(object, historicalData) {
|
||||||
|
// Only process one request at a time
|
||||||
|
if (requestTime === this.lastRequestTime) {
|
||||||
|
console.log('Processing request');
|
||||||
|
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
||||||
|
processData.call(this, historicalData, 0, [], limitEvaluator);
|
||||||
|
} else {
|
||||||
|
console.log('Ignoring returned data because of staleness');
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestData (object) {
|
||||||
|
return openmct.telemetry.request(object, {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
}).then(makeTableRows.bind(this, object))
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
this.$timeout.cancel(this.timeoutHandle);
|
||||||
|
|
||||||
|
if (objects.length > 0){
|
||||||
|
objects.forEach(requestData.bind(this));
|
||||||
|
} else {
|
||||||
|
scope.loading = false;
|
||||||
|
console.log('Resolved promise');
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param objects
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
|
||||||
|
var telemetryApi = this.openmct.telemetry;
|
||||||
|
//Set table max length to avoid unbounded growth.
|
||||||
|
var maxRows = 100000;
|
||||||
|
|
||||||
|
this.subscriptions.forEach(function (subscription) {
|
||||||
|
subscription();
|
||||||
|
});
|
||||||
|
this.subscriptions = [];
|
||||||
|
|
||||||
|
function newData(domainObject, datum) {
|
||||||
|
this.$scope.rows.push(this.table.getRowValues(
|
||||||
|
telemetryApi.limitEvaluator(domainObject), datum));
|
||||||
|
|
||||||
|
//Inform table that a new row has been added
|
||||||
|
if (this.$scope.rows.length > maxRows) {
|
||||||
|
this.$scope.$broadcast('remove:row', 0);
|
||||||
|
this.$scope.rows.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$scope.$broadcast('add:row',
|
||||||
|
this.$scope.rows.length - 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
objects.forEach(function (object){
|
||||||
|
this.subscriptions.push(
|
||||||
|
telemetryApi.subscribe(object, newData.bind(this, object), {}));
|
||||||
|
console.log('subscribed');
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
TelemetryTableController.prototype.getData = function () {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
var compositionApi = this.openmct.composition;
|
var compositionApi = this.openmct.composition;
|
||||||
var subscriptions = this.subscriptions;
|
|
||||||
var tableConfiguration = this.table;
|
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var maxRows = 100000;
|
|
||||||
var conductor = this.conductor;
|
|
||||||
var newObject = this.newObject;
|
var newObject = this.newObject;
|
||||||
|
|
||||||
this.$scope.loading = true;
|
this.$scope.loading = true;
|
||||||
|
|
||||||
function makeTableRows(object, historicalData){
|
|
||||||
var limitEvaluator = telemetryApi.limitEvaluator(object);
|
|
||||||
return historicalData.map(tableConfiguration.getRowValues.bind(tableConfiguration, limitEvaluator));
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestData(objects) {
|
|
||||||
var bounds = conductor.bounds();
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
objects.map(function (object) {
|
|
||||||
return telemetryApi.request(object, {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end
|
|
||||||
}).then(
|
|
||||||
makeTableRows.bind(this, object)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addHistoricalData(historicalData){
|
|
||||||
scope.rows = Array.prototype.concat.apply([], historicalData);
|
|
||||||
scope.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newData(domainObject, datum) {
|
|
||||||
scope.rows.push(tableConfiguration.getRowValues(datum, telemetryApi.limitEvaluator(domainObject)));
|
|
||||||
|
|
||||||
//Inform table that a new row has been added
|
|
||||||
if (scope.rows.length > maxRows) {
|
|
||||||
scope.$broadcast('remove:row', 0);
|
|
||||||
scope.rows.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.$broadcast('add:row',
|
|
||||||
scope.rows.length - 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribe(objects) {
|
|
||||||
objects.forEach(function (object){
|
|
||||||
subscriptions.push(telemetryApi.subscribe(object, newData.bind(this, object), {}));
|
|
||||||
});
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(e) {
|
function error(e) {
|
||||||
throw e;
|
scope.loading = false;
|
||||||
}
|
console.error(e);
|
||||||
|
|
||||||
function loadColumns(objects) {
|
|
||||||
var metadatas = objects.map(telemetryApi.getMetadata.bind(telemetryApi));
|
|
||||||
var allColumns = telemetryApi.commonValuesForHints(metadatas, []);
|
|
||||||
|
|
||||||
tableConfiguration.populateColumns(allColumns);
|
|
||||||
|
|
||||||
this.timeColumns = telemetryApi.commonValuesForHints(metadatas, ['x']).map(function (metadatum){
|
|
||||||
return metadatum.name;
|
|
||||||
});
|
|
||||||
|
|
||||||
self.filterColumns();
|
|
||||||
|
|
||||||
return Promise.resolve(objects);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterForTelemetry(objects){
|
function filterForTelemetry(objects){
|
||||||
@ -248,18 +373,10 @@ define(
|
|||||||
|
|
||||||
getDomainObjects()
|
getDomainObjects()
|
||||||
.then(filterForTelemetry)
|
.then(filterForTelemetry)
|
||||||
|
.then(this.loadColumns)
|
||||||
|
//.then(this.subscribeToNewData)
|
||||||
|
.then(this.getHistoricalData)
|
||||||
.catch(error)
|
.catch(error)
|
||||||
.then(function (objects){
|
|
||||||
if (objects.length > 0){
|
|
||||||
return loadColumns(objects)
|
|
||||||
.then(subscribe)
|
|
||||||
.then(requestData)
|
|
||||||
.then(addHistoricalData)
|
|
||||||
.catch(error);
|
|
||||||
} else {
|
|
||||||
scope.loading = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,13 +77,13 @@ define(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function MCTTable($timeout) {
|
function MCTTable() {
|
||||||
return {
|
return {
|
||||||
restrict: "E",
|
restrict: "E",
|
||||||
template: TableTemplate,
|
template: TableTemplate,
|
||||||
controller: [
|
controller: [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$timeout',
|
'$window',
|
||||||
'$element',
|
'$element',
|
||||||
'exportService',
|
'exportService',
|
||||||
'formatService',
|
'formatService',
|
||||||
@ -104,7 +104,7 @@ define(
|
|||||||
timeColumns: "=?",
|
timeColumns: "=?",
|
||||||
// Indicate a column to sort on. Allows control of sort
|
// Indicate a column to sort on. Allows control of sort
|
||||||
// via configuration (eg. for default sort column).
|
// via configuration (eg. for default sort column).
|
||||||
sortColumn: "=?"
|
defaultSort: "=?"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,18 @@ define([
|
|||||||
|
|
||||||
// TODO: needs reference to formatService;
|
// TODO: needs reference to formatService;
|
||||||
function TelemetryValueFormatter(valueMetadata, formatService) {
|
function TelemetryValueFormatter(valueMetadata, formatService) {
|
||||||
|
var numberFormatter = {
|
||||||
|
parse: function (x) {
|
||||||
|
return Number(x);
|
||||||
|
},
|
||||||
|
format: function (x) {
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
validate: function (x) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.valueMetadata = valueMetadata;
|
this.valueMetadata = valueMetadata;
|
||||||
this.parseCache = new WeakMap();
|
this.parseCache = new WeakMap();
|
||||||
this.formatCache = new WeakMap();
|
this.formatCache = new WeakMap();
|
||||||
@ -36,17 +48,7 @@ define([
|
|||||||
.getFormat(valueMetadata.format, valueMetadata);
|
.getFormat(valueMetadata.format, valueMetadata);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Better formatting
|
// TODO: Better formatting
|
||||||
this.formatter = {
|
this.formatter = numberFormatter;
|
||||||
parse: function (x) {
|
|
||||||
return Number(x);
|
|
||||||
},
|
|
||||||
format: function (x) {
|
|
||||||
return x;
|
|
||||||
},
|
|
||||||
validate: function (x) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueMetadata.type === 'enum') {
|
if (valueMetadata.type === 'enum') {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user