mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 05:07:52 +00:00
[Telemetry Collections] Add Telemetry Collection Functionality to Telemetry API (#3689)
Adds telemetry collections to the telemetry API Co-authored-by: Shefali Joshi <simplyrender@gmail.com> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
2564e75fc9
commit
f3fc991a74
@ -63,7 +63,7 @@ define([
|
|||||||
|
|
||||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = options.end;
|
var end = Math.min(Date.now(), options.end); // no future values
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
var duration = domainObject.telemetry.duration * 1000;
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
if (options.strategy === 'latest' || options.size === 1) {
|
||||||
start = end;
|
start = end;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { TelemetryCollection } = require("./TelemetryCollection");
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'../../plugins/displayLayout/CustomStringFormatter',
|
'../../plugins/displayLayout/CustomStringFormatter',
|
||||||
'./TelemetryMetadataManager',
|
'./TelemetryMetadataManager',
|
||||||
@ -273,6 +275,28 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request telemetry collection for a domain object.
|
||||||
|
* The `options` argument allows you to specify filters
|
||||||
|
* (start, end, etc.), sort order, and strategies for retrieving
|
||||||
|
* telemetry (aggregation, latest available, etc.).
|
||||||
|
*
|
||||||
|
* @method requestTelemetryCollection
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the object
|
||||||
|
* which has associated telemetry
|
||||||
|
* @param {module:openmct.TelemetryAPI~TelemetryRequest} options
|
||||||
|
* options for this telemetry collection request
|
||||||
|
* @returns {TelemetryCollection} a TelemetryCollection instance
|
||||||
|
*/
|
||||||
|
TelemetryAPI.prototype.requestTelemetryCollection = function (domainObject, options = {}) {
|
||||||
|
return new TelemetryCollection(
|
||||||
|
this.openmct,
|
||||||
|
domainObject,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request historical telemetry for a domain object.
|
* Request historical telemetry for a domain object.
|
||||||
* The `options` argument allows you to specify filters
|
* The `options` argument allows you to specify filters
|
||||||
|
File diff suppressed because it is too large
Load Diff
366
src/api/telemetry/TelemetryCollection.js
Normal file
366
src/api/telemetry/TelemetryCollection.js
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
/** Class representing a Telemetry Collection. */
|
||||||
|
|
||||||
|
export class TelemetryCollection extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* Creates a Telemetry Collection
|
||||||
|
*
|
||||||
|
* @param {object} openmct - Openm MCT
|
||||||
|
* @param {object} domainObject - Domain Object to user for telemetry collection
|
||||||
|
* @param {object} options - Any options passed in for request/subscribe
|
||||||
|
*/
|
||||||
|
constructor(openmct, domainObject, options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.boundedTelemetry = [];
|
||||||
|
this.futureBuffer = [];
|
||||||
|
this.parseTime = undefined;
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
|
this.unsubscribe = undefined;
|
||||||
|
this.historicalProvider = undefined;
|
||||||
|
this.options = options;
|
||||||
|
this.pageState = undefined;
|
||||||
|
this.lastBounds = undefined;
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will start the requests for historical and realtime data,
|
||||||
|
* as well as setting up initial values and watchers
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
if (this.loaded) {
|
||||||
|
throw new Error('Telemetry Collection has already been loaded.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._timeSystem(this.openmct.time.timeSystem());
|
||||||
|
this.lastBounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
this._watchBounds();
|
||||||
|
this._watchTimeSystem();
|
||||||
|
|
||||||
|
this._initiateHistoricalRequests();
|
||||||
|
this._initiateSubscriptionTelemetry();
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can/should be called by the requester of the telemetry collection
|
||||||
|
* to remove any listeners
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.requestAbort) {
|
||||||
|
this.requestAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._unwatchBounds();
|
||||||
|
this._unwatchTimeSystem();
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will start the requests for historical and realtime data,
|
||||||
|
* as well as setting up initial values and watchers
|
||||||
|
*/
|
||||||
|
getAll() {
|
||||||
|
return this.boundedTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the telemetry collection for historical requests,
|
||||||
|
* this uses the "standardizeRequestOptions" from Telemetry API
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initiateHistoricalRequests() {
|
||||||
|
this.openmct.telemetry.standardizeRequestOptions(this.options);
|
||||||
|
this.historicalProvider = this.openmct.telemetry.
|
||||||
|
findRequestProvider(this.domainObject, this.options);
|
||||||
|
|
||||||
|
this._requestHistoricalTelemetry();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* If a historical provider exists, then historical requests will be made
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _requestHistoricalTelemetry() {
|
||||||
|
if (!this.historicalProvider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let historicalData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.requestAbort = new AbortController();
|
||||||
|
this.options.abortSignal = this.requestAbort.signal;
|
||||||
|
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error requesting telemetry data...');
|
||||||
|
this.requestAbort = undefined;
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._processNewTelemetry(historicalData);
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This uses the built in subscription function from Telemetry API
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initiateSubscriptionTelemetry() {
|
||||||
|
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unsubscribe = this.openmct.telemetry
|
||||||
|
.subscribe(
|
||||||
|
this.domainObject,
|
||||||
|
datum => this._processNewTelemetry(datum),
|
||||||
|
this.options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter any new telemetry (add/page, historical, subscription) based on
|
||||||
|
* time bounds and dupes
|
||||||
|
*
|
||||||
|
* @param {(Object|Object[])} telemetryData - telemetry data object or
|
||||||
|
* array of telemetry data objects
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_processNewTelemetry(telemetryData) {
|
||||||
|
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
||||||
|
let parsedValue;
|
||||||
|
let beforeStartOfBounds;
|
||||||
|
let afterEndOfBounds;
|
||||||
|
let added = [];
|
||||||
|
|
||||||
|
for (let datum of data) {
|
||||||
|
parsedValue = this.parseTime(datum);
|
||||||
|
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||||
|
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||||
|
|
||||||
|
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||||
|
let isDuplicate = false;
|
||||||
|
let startIndex = this._sortedIndex(datum);
|
||||||
|
let endIndex = undefined;
|
||||||
|
|
||||||
|
// dupe check
|
||||||
|
if (startIndex !== this.boundedTelemetry.length) {
|
||||||
|
endIndex = _.sortedLastIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
datum,
|
||||||
|
boundedDatum => this.parseTime(boundedDatum)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (endIndex > startIndex) {
|
||||||
|
let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
isDuplicate = potentialDupes.some(_.isEqual(undefined, datum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
let index = endIndex || startIndex;
|
||||||
|
|
||||||
|
this.boundedTelemetry.splice(index, 0, datum);
|
||||||
|
added.push(datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (afterEndOfBounds) {
|
||||||
|
this.futureBuffer.push(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added.length) {
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the correct insertion point for the given telemetry datum.
|
||||||
|
* Leverages lodash's `sortedIndexBy` function which implements a binary search.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sortedIndex(datum) {
|
||||||
|
if (this.boundedTelemetry.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedValue = this.parseTime(datum);
|
||||||
|
let lastValue = this.parseTime(this.boundedTelemetry[this.boundedTelemetry.length - 1]);
|
||||||
|
|
||||||
|
if (parsedValue > lastValue || parsedValue === lastValue) {
|
||||||
|
return this.boundedTelemetry.length;
|
||||||
|
} else {
|
||||||
|
return _.sortedIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
datum,
|
||||||
|
boundedDatum => this.parseTime(boundedDatum)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when the start time, end time, or both have been updated.
|
||||||
|
* data could be added OR removed here we update the current
|
||||||
|
* bounded telemetry
|
||||||
|
*
|
||||||
|
* @param {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @param {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_bounds(bounds, isTick) {
|
||||||
|
let startChanged = this.lastBounds.start !== bounds.start;
|
||||||
|
let endChanged = this.lastBounds.end !== bounds.end;
|
||||||
|
|
||||||
|
this.lastBounds = bounds;
|
||||||
|
|
||||||
|
if (isTick) {
|
||||||
|
// need to check futureBuffer and need to check
|
||||||
|
// if anything has fallen out of bounds
|
||||||
|
let startIndex = 0;
|
||||||
|
let endIndex = 0;
|
||||||
|
|
||||||
|
let discarded = [];
|
||||||
|
let added = [];
|
||||||
|
let testDatum = {};
|
||||||
|
|
||||||
|
if (startChanged) {
|
||||||
|
testDatum[this.timeKey] = bounds.start;
|
||||||
|
// Calculate the new index of the first item within the bounds
|
||||||
|
startIndex = _.sortedIndexBy(
|
||||||
|
this.boundedTelemetry,
|
||||||
|
testDatum,
|
||||||
|
datum => this.parseTime(datum)
|
||||||
|
);
|
||||||
|
discarded = this.boundedTelemetry.splice(0, startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endChanged) {
|
||||||
|
testDatum[this.timeKey] = bounds.end;
|
||||||
|
// Calculate the new index of the last item in bounds
|
||||||
|
endIndex = _.sortedLastIndexBy(
|
||||||
|
this.futureBuffer,
|
||||||
|
testDatum,
|
||||||
|
datum => this.parseTime(datum)
|
||||||
|
);
|
||||||
|
added = this.futureBuffer.splice(0, endIndex);
|
||||||
|
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discarded.length > 0) {
|
||||||
|
this.emit('remove', discarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added.length > 0) {
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// user bounds change, reset
|
||||||
|
this._reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whenever the time system is updated need to update related values in
|
||||||
|
* the Telemetry Collection and reset the telemetry collection
|
||||||
|
*
|
||||||
|
* @param {TimeSystem} timeSystem - the value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_timeSystem(timeSystem) {
|
||||||
|
this.timeKey = timeSystem.key;
|
||||||
|
let metadataValue = this.metadata.value(this.timeKey) || { format: this.timeKey };
|
||||||
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
this.parseTime = (datum) => {
|
||||||
|
return valueFormatter.parse(datum);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the telemetry data of the collection, and re-request
|
||||||
|
* historical telemetry
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @todo handle subscriptions more granually
|
||||||
|
*/
|
||||||
|
_reset() {
|
||||||
|
this.boundedTelemetry = [];
|
||||||
|
this.futureBuffer = [];
|
||||||
|
|
||||||
|
this._requestHistoricalTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds the _bounds callback to the 'bounds' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_watchBounds() {
|
||||||
|
this.openmct.time.on('bounds', this._bounds, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes the _bounds callback from the 'bounds' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_unwatchBounds() {
|
||||||
|
this.openmct.time.off('bounds', this._bounds, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_watchTimeSystem() {
|
||||||
|
this.openmct.time.on('timeSystem', this._timeSystem, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_unwatchTimeSystem() {
|
||||||
|
this.openmct.time.off('timeSystem', this._timeSystem, this);
|
||||||
|
}
|
||||||
|
}
|
@ -23,20 +23,18 @@
|
|||||||
define([
|
define([
|
||||||
'EventEmitter',
|
'EventEmitter',
|
||||||
'lodash',
|
'lodash',
|
||||||
'./collections/BoundedTableRowCollection',
|
'./collections/TableRowCollection',
|
||||||
'./collections/FilteredTableRowCollection',
|
|
||||||
'./TelemetryTableNameColumn',
|
|
||||||
'./TelemetryTableRow',
|
'./TelemetryTableRow',
|
||||||
|
'./TelemetryTableNameColumn',
|
||||||
'./TelemetryTableColumn',
|
'./TelemetryTableColumn',
|
||||||
'./TelemetryTableUnitColumn',
|
'./TelemetryTableUnitColumn',
|
||||||
'./TelemetryTableConfiguration'
|
'./TelemetryTableConfiguration'
|
||||||
], function (
|
], function (
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
_,
|
_,
|
||||||
BoundedTableRowCollection,
|
TableRowCollection,
|
||||||
FilteredTableRowCollection,
|
|
||||||
TelemetryTableNameColumn,
|
|
||||||
TelemetryTableRow,
|
TelemetryTableRow,
|
||||||
|
TelemetryTableNameColumn,
|
||||||
TelemetryTableColumn,
|
TelemetryTableColumn,
|
||||||
TelemetryTableUnitColumn,
|
TelemetryTableUnitColumn,
|
||||||
TelemetryTableConfiguration
|
TelemetryTableConfiguration
|
||||||
@ -48,20 +46,23 @@ define([
|
|||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.rowCount = 100;
|
this.rowCount = 100;
|
||||||
this.subscriptions = {};
|
|
||||||
this.tableComposition = undefined;
|
this.tableComposition = undefined;
|
||||||
this.telemetryObjects = [];
|
|
||||||
this.datumCache = [];
|
this.datumCache = [];
|
||||||
this.outstandingRequests = 0;
|
|
||||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
|
this.telemetryObjects = {};
|
||||||
|
this.telemetryCollections = {};
|
||||||
|
this.delayedActions = [];
|
||||||
|
this.outstandingRequests = 0;
|
||||||
|
|
||||||
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||||
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
||||||
|
this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this);
|
||||||
|
this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this);
|
||||||
this.isTelemetryObject = this.isTelemetryObject.bind(this);
|
this.isTelemetryObject = this.isTelemetryObject.bind(this);
|
||||||
this.refreshData = this.refreshData.bind(this);
|
this.refreshData = this.refreshData.bind(this);
|
||||||
this.requestDataFor = this.requestDataFor.bind(this);
|
|
||||||
this.updateFilters = this.updateFilters.bind(this);
|
this.updateFilters = this.updateFilters.bind(this);
|
||||||
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
|
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
|
||||||
|
|
||||||
@ -102,8 +103,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
createTableRowCollections() {
|
createTableRowCollections() {
|
||||||
this.boundedRows = new BoundedTableRowCollection(this.openmct);
|
this.tableRows = new TableRowCollection();
|
||||||
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
|
|
||||||
|
|
||||||
//Fetch any persisted default sort
|
//Fetch any persisted default sort
|
||||||
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
||||||
@ -113,11 +113,14 @@ define([
|
|||||||
key: this.openmct.time.timeSystem().key,
|
key: this.openmct.time.timeSystem().key,
|
||||||
direction: 'asc'
|
direction: 'asc'
|
||||||
};
|
};
|
||||||
this.filteredRows.sortBy(sortOptions);
|
|
||||||
|
this.tableRows.sortBy(sortOptions);
|
||||||
|
this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadComposition() {
|
loadComposition() {
|
||||||
this.tableComposition = this.openmct.composition.get(this.domainObject);
|
this.tableComposition = this.openmct.composition.get(this.domainObject);
|
||||||
|
|
||||||
if (this.tableComposition !== undefined) {
|
if (this.tableComposition !== undefined) {
|
||||||
this.tableComposition.load().then((composition) => {
|
this.tableComposition.load().then((composition) => {
|
||||||
|
|
||||||
@ -132,66 +135,64 @@ define([
|
|||||||
|
|
||||||
addTelemetryObject(telemetryObject) {
|
addTelemetryObject(telemetryObject) {
|
||||||
this.addColumnsForObject(telemetryObject, true);
|
this.addColumnsForObject(telemetryObject, true);
|
||||||
this.requestDataFor(telemetryObject);
|
|
||||||
this.subscribeTo(telemetryObject);
|
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
this.telemetryObjects.push(telemetryObject);
|
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
|
||||||
|
let columnMap = this.getColumnMapForObject(keyString);
|
||||||
|
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||||
|
|
||||||
|
this.incrementOutstandingRequests();
|
||||||
|
|
||||||
|
const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator);
|
||||||
|
const telemetryRemover = this.getTelemetryRemover();
|
||||||
|
|
||||||
|
this.removeTelemetryCollection(keyString);
|
||||||
|
|
||||||
|
this.telemetryCollections[keyString] = this.openmct.telemetry
|
||||||
|
.requestTelemetryCollection(telemetryObject, requestOptions);
|
||||||
|
|
||||||
|
this.telemetryCollections[keyString].on('remove', telemetryRemover);
|
||||||
|
this.telemetryCollections[keyString].on('add', telemetryProcessor);
|
||||||
|
this.telemetryCollections[keyString].load();
|
||||||
|
|
||||||
|
this.decrementOutstandingRequests();
|
||||||
|
|
||||||
|
this.telemetryObjects[keyString] = {
|
||||||
|
telemetryObject,
|
||||||
|
keyString,
|
||||||
|
requestOptions,
|
||||||
|
columnMap,
|
||||||
|
limitEvaluator
|
||||||
|
};
|
||||||
|
|
||||||
this.emit('object-added', telemetryObject);
|
this.emit('object-added', telemetryObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFilters(updatedFilters) {
|
getTelemetryProcessor(keyString, columnMap, limitEvaluator) {
|
||||||
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
return (telemetry) => {
|
||||||
|
//Check that telemetry object has not been removed since telemetry was requested.
|
||||||
|
if (!this.telemetryObjects[keyString]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
let telemetryRows = telemetry.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||||
this.filters = deepCopiedFilters;
|
|
||||||
this.clearAndResubscribe();
|
if (this.paused) {
|
||||||
} else {
|
this.delayedActions.push(this.tableRows.addRows.bind(this, telemetryRows, 'add'));
|
||||||
this.filters = deepCopiedFilters;
|
} else {
|
||||||
}
|
this.tableRows.addRows(telemetryRows, 'add');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAndResubscribe() {
|
getTelemetryRemover() {
|
||||||
this.filteredRows.clear();
|
return (telemetry) => {
|
||||||
this.boundedRows.clear();
|
if (this.paused) {
|
||||||
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
this.delayedActions.push(this.tableRows.removeRowsByData.bind(this, telemetry));
|
||||||
|
} else {
|
||||||
this.telemetryObjects.forEach(this.requestDataFor.bind(this));
|
this.tableRows.removeRowsByData(telemetry);
|
||||||
this.telemetryObjects.forEach(this.subscribeTo.bind(this));
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
removeTelemetryObject(objectIdentifier) {
|
|
||||||
this.configuration.removeColumnsForObject(objectIdentifier, true);
|
|
||||||
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
|
||||||
this.boundedRows.removeAllRowsForObject(keyString);
|
|
||||||
this.unsubscribe(keyString);
|
|
||||||
this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(objectIdentifier, object.identifier));
|
|
||||||
|
|
||||||
this.emit('object-removed', objectIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDataFor(telemetryObject) {
|
|
||||||
this.incrementOutstandingRequests();
|
|
||||||
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
|
|
||||||
|
|
||||||
return this.openmct.telemetry.request(telemetryObject, requestOptions)
|
|
||||||
.then(telemetryData => {
|
|
||||||
//Check that telemetry object has not been removed since telemetry was requested.
|
|
||||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
|
||||||
let columnMap = this.getColumnMapForObject(keyString);
|
|
||||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
|
||||||
this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator);
|
|
||||||
}).finally(() => {
|
|
||||||
this.decrementOutstandingRequests();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
|
|
||||||
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
|
||||||
this.boundedRows.add(telemetryRows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,35 +217,72 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// will pull all necessary information for all existing bounded telemetry
|
||||||
|
// and pass to table row collection to reset without making any new requests
|
||||||
|
// triggered by filtering
|
||||||
|
resetRowsFromAllData() {
|
||||||
|
let allRows = [];
|
||||||
|
|
||||||
|
Object.keys(this.telemetryCollections).forEach(keyString => {
|
||||||
|
let { columnMap, limitEvaluator } = this.telemetryObjects[keyString];
|
||||||
|
|
||||||
|
this.telemetryCollections[keyString].getAll().forEach(datum => {
|
||||||
|
allRows.push(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tableRows.addRows(allRows, 'filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilters(updatedFilters) {
|
||||||
|
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
|
||||||
|
|
||||||
|
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
|
||||||
|
this.filters = deepCopiedFilters;
|
||||||
|
this.tableRows.clear();
|
||||||
|
this.clearAndResubscribe();
|
||||||
|
} else {
|
||||||
|
this.filters = deepCopiedFilters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAndResubscribe() {
|
||||||
|
let objectKeys = Object.keys(this.telemetryObjects);
|
||||||
|
|
||||||
|
this.tableRows.clear();
|
||||||
|
objectKeys.forEach((keyString) => {
|
||||||
|
this.addTelemetryObject(this.telemetryObjects[keyString].telemetryObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTelemetryObject(objectIdentifier) {
|
||||||
|
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||||
|
|
||||||
|
this.configuration.removeColumnsForObject(objectIdentifier, true);
|
||||||
|
this.tableRows.removeRowsByObject(keyString);
|
||||||
|
|
||||||
|
this.removeTelemetryCollection(keyString);
|
||||||
|
delete this.telemetryObjects[keyString];
|
||||||
|
|
||||||
|
this.emit('object-removed', objectIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
refreshData(bounds, isTick) {
|
refreshData(bounds, isTick) {
|
||||||
if (!isTick && this.outstandingRequests === 0) {
|
if (!isTick && this.tableRows.outstandingRequests === 0) {
|
||||||
this.filteredRows.clear();
|
this.tableRows.clear();
|
||||||
this.boundedRows.clear();
|
this.tableRows.sortBy({
|
||||||
this.boundedRows.sortByTimeSystem(this.openmct.time.timeSystem());
|
key: this.openmct.time.timeSystem().key,
|
||||||
this.telemetryObjects.forEach(this.requestDataFor);
|
direction: 'asc'
|
||||||
|
});
|
||||||
|
this.tableRows.resubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearData() {
|
clearData() {
|
||||||
this.filteredRows.clear();
|
this.tableRows.clear();
|
||||||
this.boundedRows.clear();
|
|
||||||
this.emit('refresh');
|
this.emit('refresh');
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnMapForObject(objectKeyString) {
|
|
||||||
let columns = this.configuration.getColumns();
|
|
||||||
|
|
||||||
if (columns[objectKeyString]) {
|
|
||||||
return columns[objectKeyString].reduce((map, column) => {
|
|
||||||
map[column.getKey()] = column;
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
addColumnsForObject(telemetryObject) {
|
addColumnsForObject(telemetryObject) {
|
||||||
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||||
|
|
||||||
@ -264,54 +302,18 @@ define([
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createColumn(metadatum) {
|
getColumnMapForObject(objectKeyString) {
|
||||||
return new TelemetryTableColumn(this.openmct, metadatum);
|
let columns = this.configuration.getColumns();
|
||||||
}
|
|
||||||
|
|
||||||
createUnitColumn(metadatum) {
|
if (columns[objectKeyString]) {
|
||||||
return new TelemetryTableUnitColumn(this.openmct, metadatum);
|
return columns[objectKeyString].reduce((map, column) => {
|
||||||
}
|
map[column.getKey()] = column;
|
||||||
|
|
||||||
subscribeTo(telemetryObject) {
|
return map;
|
||||||
let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject);
|
}, {});
|
||||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
}
|
||||||
let columnMap = this.getColumnMapForObject(keyString);
|
|
||||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
|
||||||
|
|
||||||
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
|
return {};
|
||||||
//Check that telemetry object has not been removed since telemetry was requested.
|
|
||||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.paused) {
|
|
||||||
let realtimeDatum = {
|
|
||||||
datum,
|
|
||||||
columnMap,
|
|
||||||
keyString,
|
|
||||||
limitEvaluator
|
|
||||||
};
|
|
||||||
|
|
||||||
this.datumCache.push(realtimeDatum);
|
|
||||||
} else {
|
|
||||||
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
|
|
||||||
}
|
|
||||||
}, subscribeOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
processDatumCache() {
|
|
||||||
this.datumCache.forEach(cachedDatum => {
|
|
||||||
this.processRealtimeDatum(cachedDatum.datum, cachedDatum.columnMap, cachedDatum.keyString, cachedDatum.limitEvaluator);
|
|
||||||
});
|
|
||||||
this.datumCache = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) {
|
|
||||||
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
|
||||||
}
|
|
||||||
|
|
||||||
isTelemetryObject(domainObject) {
|
|
||||||
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildOptionsFromConfiguration(telemetryObject) {
|
buildOptionsFromConfiguration(telemetryObject) {
|
||||||
@ -323,13 +325,20 @@ define([
|
|||||||
return {filters} || {};
|
return {filters} || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(keyString) {
|
createColumn(metadatum) {
|
||||||
this.subscriptions[keyString]();
|
return new TelemetryTableColumn(this.openmct, metadatum);
|
||||||
delete this.subscriptions[keyString];
|
}
|
||||||
|
|
||||||
|
createUnitColumn(metadatum) {
|
||||||
|
return new TelemetryTableUnitColumn(this.openmct, metadatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
isTelemetryObject(domainObject) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
|
||||||
}
|
}
|
||||||
|
|
||||||
sortBy(sortOptions) {
|
sortBy(sortOptions) {
|
||||||
this.filteredRows.sortBy(sortOptions);
|
this.tableRows.sortBy(sortOptions);
|
||||||
|
|
||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
let configuration = this.configuration.getConfiguration();
|
let configuration = this.configuration.getConfiguration();
|
||||||
@ -338,21 +347,36 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runDelayedActions() {
|
||||||
|
this.delayedActions.forEach(action => action());
|
||||||
|
this.delayedActions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTelemetryCollection(keyString) {
|
||||||
|
if (this.telemetryCollections[keyString]) {
|
||||||
|
this.telemetryCollections[keyString].destroy();
|
||||||
|
this.telemetryCollections[keyString] = undefined;
|
||||||
|
delete this.telemetryCollections[keyString];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
this.paused = true;
|
this.paused = true;
|
||||||
this.boundedRows.unsubscribeFromBounds();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unpause() {
|
unpause() {
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
this.processDatumCache();
|
this.runDelayedActions();
|
||||||
this.boundedRows.subscribeToBounds();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.boundedRows.destroy();
|
this.tableRows.destroy();
|
||||||
this.filteredRows.destroy();
|
|
||||||
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
|
||||||
|
|
||||||
|
let keystrings = Object.keys(this.telemetryCollections);
|
||||||
|
keystrings.forEach(this.removeTelemetryCollection);
|
||||||
|
|
||||||
this.openmct.time.off('bounds', this.refreshData);
|
this.openmct.time.off('bounds', this.refreshData);
|
||||||
this.openmct.time.off('timeSystem', this.refreshData);
|
this.openmct.time.off('timeSystem', this.refreshData);
|
||||||
|
|
||||||
|
@ -1,166 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'lodash',
|
|
||||||
'./SortedTableRowCollection'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
_,
|
|
||||||
SortedTableRowCollection
|
|
||||||
) {
|
|
||||||
|
|
||||||
class BoundedTableRowCollection extends SortedTableRowCollection {
|
|
||||||
constructor(openmct) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.futureBuffer = new SortedTableRowCollection();
|
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
this.sortByTimeSystem = this.sortByTimeSystem.bind(this);
|
|
||||||
this.bounds = this.bounds.bind(this);
|
|
||||||
|
|
||||||
this.sortByTimeSystem(openmct.time.timeSystem());
|
|
||||||
|
|
||||||
this.lastBounds = openmct.time.bounds();
|
|
||||||
|
|
||||||
this.subscribeToBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
addOne(item) {
|
|
||||||
let parsedValue = this.getValueForSortColumn(item);
|
|
||||||
// Insert into either in-bounds array, or the future buffer.
|
|
||||||
// Data in the future buffer will be re-evaluated for possible
|
|
||||||
// insertion on next bounds change
|
|
||||||
let beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
|
||||||
let afterEndOfBounds = parsedValue > this.lastBounds.end;
|
|
||||||
|
|
||||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
|
||||||
return super.addOne(item);
|
|
||||||
} else if (afterEndOfBounds) {
|
|
||||||
this.futureBuffer.addOne(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sortByTimeSystem(timeSystem) {
|
|
||||||
this.sortBy({
|
|
||||||
key: timeSystem.key,
|
|
||||||
direction: 'asc'
|
|
||||||
});
|
|
||||||
let formatter = this.openmct.telemetry.getValueFormatter({
|
|
||||||
key: timeSystem.key,
|
|
||||||
source: timeSystem.key,
|
|
||||||
format: timeSystem.timeFormat
|
|
||||||
});
|
|
||||||
this.parseTime = formatter.parse.bind(formatter);
|
|
||||||
this.futureBuffer.sortBy({
|
|
||||||
key: timeSystem.key,
|
|
||||||
direction: 'asc'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is optimized for ticking - it assumes that start and end
|
|
||||||
* bounds will only increase and as such this cannot be used for decreasing
|
|
||||||
* bounds changes.
|
|
||||||
*
|
|
||||||
* An implication of this is that data will not be discarded that exceeds
|
|
||||||
* the given end bounds. For arbitrary bounds changes, it's assumed that
|
|
||||||
* a telemetry requery is performed anyway, and the collection is cleared
|
|
||||||
* and repopulated.
|
|
||||||
*
|
|
||||||
* @fires TelemetryCollection#added
|
|
||||||
* @fires TelemetryCollection#discarded
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
bounds(bounds) {
|
|
||||||
let startChanged = this.lastBounds.start !== bounds.start;
|
|
||||||
let endChanged = this.lastBounds.end !== bounds.end;
|
|
||||||
|
|
||||||
let startIndex = 0;
|
|
||||||
let endIndex = 0;
|
|
||||||
|
|
||||||
let discarded = [];
|
|
||||||
let added = [];
|
|
||||||
let testValue = {
|
|
||||||
datum: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.lastBounds = bounds;
|
|
||||||
|
|
||||||
if (startChanged) {
|
|
||||||
testValue.datum[this.sortOptions.key] = bounds.start;
|
|
||||||
// Calculate the new index of the first item within the bounds
|
|
||||||
startIndex = this.sortedIndex(this.rows, testValue);
|
|
||||||
discarded = this.rows.splice(0, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endChanged) {
|
|
||||||
testValue.datum[this.sortOptions.key] = bounds.end;
|
|
||||||
// Calculate the new index of the last item in bounds
|
|
||||||
endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
|
|
||||||
added = this.futureBuffer.rows.splice(0, endIndex);
|
|
||||||
added.forEach((datum) => this.rows.push(datum));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discarded && discarded.length > 0) {
|
|
||||||
/**
|
|
||||||
* A `discarded` event is emitted when telemetry data fall out of
|
|
||||||
* bounds due to a bounds change event
|
|
||||||
* @type {object[]} discarded the telemetry data
|
|
||||||
* discarded as a result of the bounds change
|
|
||||||
*/
|
|
||||||
this.emit('remove', discarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (added && added.length > 0) {
|
|
||||||
/**
|
|
||||||
* An `added` event is emitted when a bounds change results in
|
|
||||||
* received telemetry falling within the new bounds.
|
|
||||||
* @type {object[]} added the telemetry data that is now within bounds
|
|
||||||
*/
|
|
||||||
this.emit('add', added);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getValueForSortColumn(row) {
|
|
||||||
return this.parseTime(row.datum[this.sortOptions.key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribeFromBounds() {
|
|
||||||
this.openmct.time.off('bounds', this.bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeToBounds() {
|
|
||||||
this.openmct.time.on('bounds', this.bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.unsubscribeFromBounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BoundedTableRowCollection;
|
|
||||||
});
|
|
@ -1,136 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'./SortedTableRowCollection'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
SortedTableRowCollection
|
|
||||||
) {
|
|
||||||
class FilteredTableRowCollection extends SortedTableRowCollection {
|
|
||||||
constructor(masterCollection) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.masterCollection = masterCollection;
|
|
||||||
this.columnFilters = {};
|
|
||||||
|
|
||||||
//Synchronize with master collection
|
|
||||||
this.masterCollection.on('add', this.add);
|
|
||||||
this.masterCollection.on('remove', this.remove);
|
|
||||||
|
|
||||||
//Default to master collection's sort options
|
|
||||||
this.sortOptions = masterCollection.sortBy();
|
|
||||||
}
|
|
||||||
|
|
||||||
setColumnFilter(columnKey, filter) {
|
|
||||||
filter = filter.trim().toLowerCase();
|
|
||||||
|
|
||||||
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
|
||||||
|
|
||||||
if (filter.length === 0) {
|
|
||||||
delete this.columnFilters[columnKey];
|
|
||||||
} else {
|
|
||||||
this.columnFilters[columnKey] = filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
|
||||||
this.emit('filter');
|
|
||||||
}
|
|
||||||
|
|
||||||
setColumnRegexFilter(columnKey, filter) {
|
|
||||||
filter = filter.trim();
|
|
||||||
|
|
||||||
let rowsToFilter = this.masterCollection.getRows();
|
|
||||||
|
|
||||||
this.columnFilters[columnKey] = new RegExp(filter);
|
|
||||||
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
|
||||||
this.emit('filter');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
getRowsToFilter(columnKey, filter) {
|
|
||||||
if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
|
|
||||||
return this.getRows();
|
|
||||||
} else {
|
|
||||||
return this.masterCollection.getRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
isSubsetOfCurrentFilter(columnKey, filter) {
|
|
||||||
if (this.columnFilters[columnKey] instanceof RegExp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.columnFilters[columnKey]
|
|
||||||
&& filter.startsWith(this.columnFilters[columnKey])
|
|
||||||
// startsWith check will otherwise fail when filter cleared
|
|
||||||
// because anyString.startsWith('') === true
|
|
||||||
&& filter !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
addOne(row) {
|
|
||||||
return this.matchesFilters(row) && super.addOne(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
matchesFilters(row) {
|
|
||||||
let doesMatchFilters = true;
|
|
||||||
Object.keys(this.columnFilters).forEach((key) => {
|
|
||||||
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let formattedValue = row.getFormattedValue(key);
|
|
||||||
if (formattedValue === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.columnFilters[key] instanceof RegExp) {
|
|
||||||
doesMatchFilters = this.columnFilters[key].test(formattedValue);
|
|
||||||
} else {
|
|
||||||
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return doesMatchFilters;
|
|
||||||
}
|
|
||||||
|
|
||||||
rowHasColumn(row, key) {
|
|
||||||
return Object.prototype.hasOwnProperty.call(row.columns, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.masterCollection.off('add', this.add);
|
|
||||||
this.masterCollection.off('remove', this.remove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FilteredTableRowCollection;
|
|
||||||
});
|
|
@ -36,85 +36,72 @@ define(
|
|||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
class SortedTableRowCollection extends EventEmitter {
|
class TableRowCollection extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.dupeCheck = false;
|
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
|
this.columnFilters = {};
|
||||||
|
this.addRows = this.addRows.bind(this);
|
||||||
|
this.removeRowsByObject = this.removeRowsByObject.bind(this);
|
||||||
|
this.removeRowsByData = this.removeRowsByData.bind(this);
|
||||||
|
|
||||||
this.add = this.add.bind(this);
|
this.clear = this.clear.bind(this);
|
||||||
this.remove = this.remove.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
removeRowsByObject(keyString) {
|
||||||
* Add a datum or array of data to this telemetry collection
|
let removed = [];
|
||||||
* @fires TelemetryCollection#added
|
|
||||||
* @param {object | object[]} rows
|
|
||||||
*/
|
|
||||||
add(rows) {
|
|
||||||
if (Array.isArray(rows)) {
|
|
||||||
this.dupeCheck = false;
|
|
||||||
|
|
||||||
let rowsAdded = rows.filter(this.addOne, this);
|
this.rows = this.rows.filter((row) => {
|
||||||
if (rowsAdded.length > 0) {
|
if (row.objectKeyString === keyString) {
|
||||||
this.emit('add', rowsAdded);
|
removed.push(row);
|
||||||
}
|
|
||||||
|
|
||||||
this.dupeCheck = true;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
let wasAdded = this.addOne(rows);
|
return true;
|
||||||
if (wasAdded) {
|
|
||||||
this.emit('add', rows);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
this.emit('remove', removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
addRows(rows, type = 'add') {
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
addOne(row) {
|
|
||||||
if (this.sortOptions === undefined) {
|
if (this.sortOptions === undefined) {
|
||||||
throw 'Please specify sort options';
|
throw 'Please specify sort options';
|
||||||
}
|
}
|
||||||
|
|
||||||
let isDuplicate = false;
|
let isFilterTriggeredReset = type === 'filter';
|
||||||
|
let anyActiveFilters = Object.keys(this.columnFilters).length > 0;
|
||||||
|
let rowsToAdd = !anyActiveFilters ? rows : rows.filter(this.matchesFilters, this);
|
||||||
|
|
||||||
// Going to check for duplicates. Bound the search problem to
|
// if type is filter, then it's a reset of all rows,
|
||||||
// items around the given time. Use sortedIndex because it
|
// need to wipe current rows
|
||||||
// employs a binary search which is O(log n). Can use binary search
|
if (isFilterTriggeredReset) {
|
||||||
// because the array is guaranteed ordered due to sorted insertion.
|
this.rows = [];
|
||||||
let startIx = this.sortedIndex(this.rows, row);
|
|
||||||
let endIx = undefined;
|
|
||||||
|
|
||||||
if (this.dupeCheck && startIx !== this.rows.length) {
|
|
||||||
endIx = this.sortedLastIndex(this.rows, row);
|
|
||||||
|
|
||||||
// Create an array of potential dupes, based on having the
|
|
||||||
// same time stamp
|
|
||||||
let potentialDupes = this.rows.slice(startIx, endIx + 1);
|
|
||||||
// Search potential dupes for exact dupe
|
|
||||||
isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, row));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
for (let row of rowsToAdd) {
|
||||||
this.rows.splice(endIx || startIx, 0, row);
|
let index = this.sortedIndex(this.rows, row);
|
||||||
|
this.rows.splice(index, 0, row);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// we emit filter no matter what to trigger
|
||||||
|
// an update of visible rows
|
||||||
|
if (rowsToAdd.length > 0 || isFilterTriggeredReset) {
|
||||||
|
this.emit(type, rowsToAdd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedLastIndex(rows, testRow) {
|
sortedLastIndex(rows, testRow) {
|
||||||
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
|
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the correct insertion point for the given row.
|
* Finds the correct insertion point for the given row.
|
||||||
* Leverages lodash's `sortedIndex` function which implements a binary search.
|
* Leverages lodash's `sortedIndex` function which implements a binary search.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
sortedIndex(rows, testRow, lodashFunction) {
|
sortedIndex(rows, testRow, lodashFunction = _.sortedIndexBy) {
|
||||||
if (this.rows.length === 0) {
|
if (this.rows.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -123,8 +110,6 @@ define(
|
|||||||
const firstValue = this.getValueForSortColumn(this.rows[0]);
|
const firstValue = this.getValueForSortColumn(this.rows[0]);
|
||||||
const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
|
const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
|
||||||
|
|
||||||
lodashFunction = lodashFunction || _.sortedIndexBy;
|
|
||||||
|
|
||||||
if (this.sortOptions.direction === 'asc') {
|
if (this.sortOptions.direction === 'asc') {
|
||||||
if (testRowValue > lastValue) {
|
if (testRowValue > lastValue) {
|
||||||
return this.rows.length;
|
return this.rows.length;
|
||||||
@ -162,6 +147,22 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeRowsByData(data) {
|
||||||
|
let removed = [];
|
||||||
|
|
||||||
|
this.rows = this.rows.filter((row) => {
|
||||||
|
if (data.includes(row.fullDatum)) {
|
||||||
|
removed.push(row);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('remove', removed);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the telemetry collection based on the provided sort field
|
* Sorts the telemetry collection based on the provided sort field
|
||||||
* specifier. Subsequent inserts are sorted to maintain specified sport
|
* specifier. Subsequent inserts are sorted to maintain specified sport
|
||||||
@ -205,6 +206,7 @@ define(
|
|||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.sortOptions = sortOptions;
|
this.sortOptions = sortOptions;
|
||||||
this.rows = _.orderBy(this.rows, (row) => row.getParsedValue(sortOptions.key), sortOptions.direction);
|
this.rows = _.orderBy(this.rows, (row) => row.getParsedValue(sortOptions.key), sortOptions.direction);
|
||||||
|
|
||||||
this.emit('sort');
|
this.emit('sort');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,44 +214,114 @@ define(
|
|||||||
return Object.assign({}, this.sortOptions);
|
return Object.assign({}, this.sortOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllRowsForObject(objectKeyString) {
|
setColumnFilter(columnKey, filter) {
|
||||||
let removed = [];
|
filter = filter.trim().toLowerCase();
|
||||||
this.rows = this.rows.filter(row => {
|
let wasBlank = this.columnFilters[columnKey] === undefined;
|
||||||
if (row.objectKeyString === objectKeyString) {
|
let isSubset = this.isSubsetOfCurrentFilter(columnKey, filter);
|
||||||
removed.push(row);
|
|
||||||
|
|
||||||
|
if (filter.length === 0) {
|
||||||
|
delete this.columnFilters[columnKey];
|
||||||
|
} else {
|
||||||
|
this.columnFilters[columnKey] = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubset || wasBlank) {
|
||||||
|
this.rows = this.rows.filter(this.matchesFilters, this);
|
||||||
|
this.emit('filter');
|
||||||
|
} else {
|
||||||
|
this.emit('resetRowsFromAllData');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setColumnRegexFilter(columnKey, filter) {
|
||||||
|
filter = filter.trim();
|
||||||
|
this.columnFilters[columnKey] = new RegExp(filter);
|
||||||
|
|
||||||
|
this.emit('resetRowsFromAllData');
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumnMapForObject(objectKeyString) {
|
||||||
|
let columns = this.configuration.getColumns();
|
||||||
|
|
||||||
|
if (columns[objectKeyString]) {
|
||||||
|
return columns[objectKeyString].reduce((map, column) => {
|
||||||
|
map[column.getKey()] = column;
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @private
|
||||||
|
// */
|
||||||
|
isSubsetOfCurrentFilter(columnKey, filter) {
|
||||||
|
if (this.columnFilters[columnKey] instanceof RegExp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.columnFilters[columnKey]
|
||||||
|
&& filter.startsWith(this.columnFilters[columnKey])
|
||||||
|
// startsWith check will otherwise fail when filter cleared
|
||||||
|
// because anyString.startsWith('') === true
|
||||||
|
&& filter !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
matchesFilters(row) {
|
||||||
|
let doesMatchFilters = true;
|
||||||
|
Object.keys(this.columnFilters).forEach((key) => {
|
||||||
|
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
let formattedValue = row.getFormattedValue(key);
|
||||||
|
if (formattedValue === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.columnFilters[key] instanceof RegExp) {
|
||||||
|
doesMatchFilters = this.columnFilters[key].test(formattedValue);
|
||||||
|
} else {
|
||||||
|
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.emit('remove', removed);
|
return doesMatchFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValueForSortColumn(row) {
|
rowHasColumn(row, key) {
|
||||||
return row.getParsedValue(this.sortOptions.key);
|
return Object.prototype.hasOwnProperty.call(row.columns, key);
|
||||||
}
|
|
||||||
|
|
||||||
remove(removedRows) {
|
|
||||||
this.rows = this.rows.filter(row => {
|
|
||||||
return removedRows.indexOf(row) === -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.emit('remove', removedRows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRows() {
|
getRows() {
|
||||||
return this.rows;
|
return this.rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRowsLength() {
|
||||||
|
return this.rows.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueForSortColumn(row) {
|
||||||
|
return row.getParsedValue(this.sortOptions.key);
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
let removedRows = this.rows;
|
let removedRows = this.rows;
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
|
|
||||||
this.emit('remove', removedRows);
|
this.emit('remove', removedRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.removeAllListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SortedTableRowCollection;
|
return TableRowCollection;
|
||||||
});
|
});
|
@ -466,22 +466,21 @@ export default {
|
|||||||
|
|
||||||
this.table.on('object-added', this.addObject);
|
this.table.on('object-added', this.addObject);
|
||||||
this.table.on('object-removed', this.removeObject);
|
this.table.on('object-removed', this.removeObject);
|
||||||
this.table.on('outstanding-requests', this.outstandingRequests);
|
|
||||||
this.table.on('refresh', this.clearRowsAndRerender);
|
this.table.on('refresh', this.clearRowsAndRerender);
|
||||||
this.table.on('historical-rows-processed', this.checkForMarkedRows);
|
this.table.on('historical-rows-processed', this.checkForMarkedRows);
|
||||||
|
this.table.on('outstanding-requests', this.outstandingRequests);
|
||||||
|
|
||||||
this.table.filteredRows.on('add', this.rowsAdded);
|
this.table.tableRows.on('add', this.rowsAdded);
|
||||||
this.table.filteredRows.on('remove', this.rowsRemoved);
|
this.table.tableRows.on('remove', this.rowsRemoved);
|
||||||
this.table.filteredRows.on('sort', this.updateVisibleRows);
|
this.table.tableRows.on('sort', this.updateVisibleRows);
|
||||||
this.table.filteredRows.on('filter', this.updateVisibleRows);
|
this.table.tableRows.on('filter', this.updateVisibleRows);
|
||||||
|
|
||||||
//Default sort
|
//Default sort
|
||||||
this.sortOptions = this.table.filteredRows.sortBy();
|
this.sortOptions = this.table.tableRows.sortBy();
|
||||||
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
|
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
|
||||||
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
|
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
|
||||||
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
|
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
|
||||||
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
|
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
|
||||||
|
|
||||||
this.table.configuration.on('change', this.updateConfiguration);
|
this.table.configuration.on('change', this.updateConfiguration);
|
||||||
|
|
||||||
this.calculateTableSize();
|
this.calculateTableSize();
|
||||||
@ -493,13 +492,14 @@ export default {
|
|||||||
destroyed() {
|
destroyed() {
|
||||||
this.table.off('object-added', this.addObject);
|
this.table.off('object-added', this.addObject);
|
||||||
this.table.off('object-removed', this.removeObject);
|
this.table.off('object-removed', this.removeObject);
|
||||||
this.table.off('outstanding-requests', this.outstandingRequests);
|
this.table.off('historical-rows-processed', this.checkForMarkedRows);
|
||||||
this.table.off('refresh', this.clearRowsAndRerender);
|
this.table.off('refresh', this.clearRowsAndRerender);
|
||||||
|
this.table.off('outstanding-requests', this.outstandingRequests);
|
||||||
|
|
||||||
this.table.filteredRows.off('add', this.rowsAdded);
|
this.table.tableRows.off('add', this.rowsAdded);
|
||||||
this.table.filteredRows.off('remove', this.rowsRemoved);
|
this.table.tableRows.off('remove', this.rowsRemoved);
|
||||||
this.table.filteredRows.off('sort', this.updateVisibleRows);
|
this.table.tableRows.off('sort', this.updateVisibleRows);
|
||||||
this.table.filteredRows.off('filter', this.updateVisibleRows);
|
this.table.tableRows.off('filter', this.updateVisibleRows);
|
||||||
|
|
||||||
this.table.configuration.off('change', this.updateConfiguration);
|
this.table.configuration.off('change', this.updateConfiguration);
|
||||||
|
|
||||||
@ -517,13 +517,13 @@ export default {
|
|||||||
|
|
||||||
let start = 0;
|
let start = 0;
|
||||||
let end = VISIBLE_ROW_COUNT;
|
let end = VISIBLE_ROW_COUNT;
|
||||||
let filteredRows = this.table.filteredRows.getRows();
|
let tableRows = this.table.tableRows.getRows();
|
||||||
let filteredRowsLength = filteredRows.length;
|
let tableRowsLength = tableRows.length;
|
||||||
|
|
||||||
this.totalNumberOfRows = filteredRowsLength;
|
this.totalNumberOfRows = tableRowsLength;
|
||||||
|
|
||||||
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
if (tableRowsLength < VISIBLE_ROW_COUNT) {
|
||||||
end = filteredRowsLength;
|
end = tableRowsLength;
|
||||||
} else {
|
} else {
|
||||||
let firstVisible = this.calculateFirstVisibleRow();
|
let firstVisible = this.calculateFirstVisibleRow();
|
||||||
let lastVisible = this.calculateLastVisibleRow();
|
let lastVisible = this.calculateLastVisibleRow();
|
||||||
@ -535,15 +535,15 @@ export default {
|
|||||||
|
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
start = 0;
|
start = 0;
|
||||||
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
end = Math.min(VISIBLE_ROW_COUNT, tableRowsLength);
|
||||||
} else if (end >= filteredRowsLength) {
|
} else if (end >= tableRowsLength) {
|
||||||
end = filteredRowsLength;
|
end = tableRowsLength;
|
||||||
start = end - VISIBLE_ROW_COUNT + 1;
|
start = end - VISIBLE_ROW_COUNT + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rowOffset = start;
|
this.rowOffset = start;
|
||||||
this.visibleRows = filteredRows.slice(start, end);
|
this.visibleRows = tableRows.slice(start, end);
|
||||||
|
|
||||||
this.updatingView = false;
|
this.updatingView = false;
|
||||||
});
|
});
|
||||||
@ -630,19 +630,19 @@ export default {
|
|||||||
filterChanged(columnKey) {
|
filterChanged(columnKey) {
|
||||||
if (this.enableRegexSearch[columnKey]) {
|
if (this.enableRegexSearch[columnKey]) {
|
||||||
if (this.isCompleteRegex(this.filters[columnKey])) {
|
if (this.isCompleteRegex(this.filters[columnKey])) {
|
||||||
this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
|
this.table.tableRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
this.table.tableRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
},
|
},
|
||||||
clearFilter(columnKey) {
|
clearFilter(columnKey) {
|
||||||
this.filters[columnKey] = '';
|
this.filters[columnKey] = '';
|
||||||
this.table.filteredRows.setColumnFilter(columnKey, '');
|
this.table.tableRows.setColumnFilter(columnKey, '');
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
},
|
},
|
||||||
rowsAdded(rows) {
|
rowsAdded(rows) {
|
||||||
@ -674,8 +674,8 @@ export default {
|
|||||||
* Calculates height based on total number of rows, and sets table height.
|
* Calculates height based on total number of rows, and sets table height.
|
||||||
*/
|
*/
|
||||||
setHeight() {
|
setHeight() {
|
||||||
let filteredRowsLength = this.table.filteredRows.getRows().length;
|
let tableRowsLength = this.table.tableRows.getRowsLength();
|
||||||
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
this.totalHeight = this.rowHeight * tableRowsLength - 1;
|
||||||
// Set element height directly to avoid having to wait for Vue to update DOM
|
// Set element height directly to avoid having to wait for Vue to update DOM
|
||||||
// which causes subsequent scroll to use an out of date height.
|
// which causes subsequent scroll to use an out of date height.
|
||||||
this.contentTable.style.height = this.totalHeight + 'px';
|
this.contentTable.style.height = this.totalHeight + 'px';
|
||||||
@ -689,13 +689,13 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportAllDataAsCSV() {
|
exportAllDataAsCSV() {
|
||||||
const justTheData = this.table.filteredRows.getRows()
|
const justTheData = this.table.tableRows.getRows()
|
||||||
.map(row => row.getFormattedDatum(this.headers));
|
.map(row => row.getFormattedDatum(this.headers));
|
||||||
|
|
||||||
this.exportAsCSV(justTheData);
|
this.exportAsCSV(justTheData);
|
||||||
},
|
},
|
||||||
exportMarkedDataAsCSV() {
|
exportMarkedDataAsCSV() {
|
||||||
const data = this.table.filteredRows.getRows()
|
const data = this.table.tableRows.getRows()
|
||||||
.filter(row => row.marked === true)
|
.filter(row => row.marked === true)
|
||||||
.map(row => row.getFormattedDatum(this.headers));
|
.map(row => row.getFormattedDatum(this.headers));
|
||||||
|
|
||||||
@ -900,7 +900,7 @@ export default {
|
|||||||
|
|
||||||
let lastRowToBeMarked = this.visibleRows[rowIndex];
|
let lastRowToBeMarked = this.visibleRows[rowIndex];
|
||||||
|
|
||||||
let allRows = this.table.filteredRows.getRows();
|
let allRows = this.table.tableRows.getRows();
|
||||||
let firstRowIndex = allRows.indexOf(this.markedRows[0]);
|
let firstRowIndex = allRows.indexOf(this.markedRows[0]);
|
||||||
let lastRowIndex = allRows.indexOf(lastRowToBeMarked);
|
let lastRowIndex = allRows.indexOf(lastRowToBeMarked);
|
||||||
|
|
||||||
@ -923,17 +923,17 @@ export default {
|
|||||||
},
|
},
|
||||||
checkForMarkedRows() {
|
checkForMarkedRows() {
|
||||||
this.isShowingMarkedRowsOnly = false;
|
this.isShowingMarkedRowsOnly = false;
|
||||||
this.markedRows = this.table.filteredRows.getRows().filter(row => row.marked);
|
this.markedRows = this.table.tableRows.getRows().filter(row => row.marked);
|
||||||
},
|
},
|
||||||
showRows(rows) {
|
showRows(rows) {
|
||||||
this.table.filteredRows.rows = rows;
|
this.table.tableRows.rows = rows;
|
||||||
this.table.filteredRows.emit('filter');
|
this.table.emit('filter');
|
||||||
},
|
},
|
||||||
toggleMarkedRows(flag) {
|
toggleMarkedRows(flag) {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
this.isShowingMarkedRowsOnly = true;
|
this.isShowingMarkedRowsOnly = true;
|
||||||
this.userScroll = this.scrollable.scrollTop;
|
this.userScroll = this.scrollable.scrollTop;
|
||||||
this.allRows = this.table.filteredRows.getRows();
|
this.allRows = this.table.tableRows.getRows();
|
||||||
|
|
||||||
this.showRows(this.markedRows);
|
this.showRows(this.markedRows);
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
|
@ -48,6 +48,8 @@ describe("the plugin", () => {
|
|||||||
let tablePlugin;
|
let tablePlugin;
|
||||||
let element;
|
let element;
|
||||||
let child;
|
let child;
|
||||||
|
let historicalProvider;
|
||||||
|
let originalRouterPath;
|
||||||
let unlistenConfigMutation;
|
let unlistenConfigMutation;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
@ -58,7 +60,12 @@ describe("the plugin", () => {
|
|||||||
tablePlugin = new TablePlugin();
|
tablePlugin = new TablePlugin();
|
||||||
openmct.install(tablePlugin);
|
openmct.install(tablePlugin);
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
historicalProvider = {
|
||||||
|
request: () => {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||||
|
|
||||||
element = document.createElement('div');
|
element = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
@ -78,6 +85,8 @@ describe("the plugin", () => {
|
|||||||
callBack();
|
callBack();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
originalRouterPath = openmct.router.path;
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
});
|
});
|
||||||
@ -190,11 +199,12 @@ describe("the plugin", () => {
|
|||||||
let telemetryPromise = new Promise((resolve) => {
|
let telemetryPromise = new Promise((resolve) => {
|
||||||
telemetryPromiseResolve = resolve;
|
telemetryPromiseResolve = resolve;
|
||||||
});
|
});
|
||||||
openmct.telemetry.request.and.callFake(() => {
|
|
||||||
|
historicalProvider.request = () => {
|
||||||
telemetryPromiseResolve(testTelemetry);
|
telemetryPromiseResolve(testTelemetry);
|
||||||
|
|
||||||
return telemetryPromise;
|
return telemetryPromise;
|
||||||
});
|
};
|
||||||
|
|
||||||
openmct.router.path = [testTelemetryObject];
|
openmct.router.path = [testTelemetryObject];
|
||||||
|
|
||||||
@ -208,6 +218,10 @@ describe("the plugin", () => {
|
|||||||
return telemetryPromise.then(() => Vue.nextTick());
|
return telemetryPromise.then(() => Vue.nextTick());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
openmct.router.path = originalRouterPath;
|
||||||
|
});
|
||||||
|
|
||||||
it("Renders a row for every telemetry datum returned", () => {
|
it("Renders a row for every telemetry datum returned", () => {
|
||||||
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
expect(rows.length).toBe(3);
|
expect(rows.length).toBe(3);
|
||||||
@ -256,14 +270,14 @@ describe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Supports filtering telemetry by regular text search", () => {
|
it("Supports filtering telemetry by regular text search", () => {
|
||||||
tableInstance.filteredRows.setColumnFilter("some-key", "1");
|
tableInstance.tableRows.setColumnFilter("some-key", "1");
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
|
||||||
expect(filteredRowElements.length).toEqual(1);
|
expect(filteredRowElements.length).toEqual(1);
|
||||||
|
|
||||||
tableInstance.filteredRows.setColumnFilter("some-key", "");
|
tableInstance.tableRows.setColumnFilter("some-key", "");
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
@ -274,14 +288,14 @@ describe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Supports filtering using Regex", () => {
|
it("Supports filtering using Regex", () => {
|
||||||
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value$");
|
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value$");
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
|
||||||
expect(filteredRowElements.length).toEqual(0);
|
expect(filteredRowElements.length).toEqual(0);
|
||||||
|
|
||||||
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value");
|
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value");
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
return Vue.nextTick().then(() => {
|
||||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
Loading…
Reference in New Issue
Block a user