Compare commits

...

47 Commits

Author SHA1 Message Date
Jamie Vigliotta
05764fbf35 added durations for historical time history 2021-04-16 15:51:46 -07:00
Jamie Vigliotta
bdc50564f2 updated telemetry table tests to handle telemetry collections 2021-04-15 13:19:39 -07:00
Jamie Vigliotta
d349315944 trying rule out tests as issues 2021-04-15 11:55:53 -07:00
Jamie Vigliotta
c5034beb9c changing circle ci config back 2021-04-15 11:27:13 -07:00
Jamie Vigliotta
5b97d96b2e testing browser versions for circle ci issue 2021-04-15 11:16:38 -07:00
Jamie Vigliotta
6a586635c3 testing if telemetry api tests are causing other issues 2021-04-15 10:55:52 -07:00
Jamie Vigliotta
fc8021c72c added very basic telemetry collection test to telemetry api tests, will need separate telemtery collection tests 2021-04-14 13:21:37 -07:00
Jamie Vigliotta
7fe19d424f fixed telemetryAPI tests and removed xdescribe 2021-04-14 11:41:49 -07:00
Jamie V
515c18278b Merge branch 'master' into telemetry-collection 2021-04-13 14:51:16 -07:00
Jamie Vigliotta
9b8518e6d2 last bit of tidying up before PR 2021-04-13 14:47:44 -07:00
Jamie Vigliotta
4639a1eabc removing debug code 2021-04-13 13:46:25 -07:00
Jamie Vigliotta
26f6407283 handling emitted arguments correctly 2021-04-13 13:37:04 -07:00
Jamie Vigliotta
b9fc3d0499 more updates for options handling 2021-04-13 12:35:18 -07:00
Jamie Vigliotta
5db71db974 cleaning up some code and handling arguments more clearly in regard to subscription vs request 2021-04-13 12:31:44 -07:00
Jamie Vigliotta
0b9bdb0c74 removing old commented code and files 2021-04-13 11:43:37 -07:00
Jamie Vigliotta
cdedea4bed another one more commit with old code commented out 2021-04-13 11:40:00 -07:00
Jamie Vigliotta
82096ea887 removed bounded row collection, moved to sorted collection, added limt on end value for state generator telemetry to prevent future telemetry 2021-04-12 17:30:34 -07:00
Jamie Vigliotta
43488646c0 removed bounded rows, moved functionality to sortedrows WIP 2021-04-09 12:32:17 -07:00
Jamie Vigliotta
71bbb67d78 WIP 2021-04-07 09:56:52 -07:00
Jamie Vigliotta
ad96e5ce66 WIP 2021-04-06 10:48:49 -07:00
Jamie Vigliotta
1dc245c9ee WIP 2021-04-05 17:30:34 -07:00
Jamie Vigliotta
c6c432005f WIP 2021-04-05 12:51:22 -07:00
Jamie Vigliotta
8a1a2bbf84 Merge branch 'master' into telemetry-collection
Merge'n master
2021-03-31 10:25:38 -07:00
Jamie Vigliotta
0cbb0e7ae2 adding some additional functionality to telemetry collectinos 2021-03-29 13:47:26 -07:00
Jamie Vigliotta
993576a471 Merge branch 'master' into telemetry-collection
Mege'n master
2021-03-29 10:30:11 -07:00
Jamie Vigliotta
d69056361f WIP 2021-03-29 10:29:30 -07:00
Jamie Vigliotta
30da83eb0a WIP 2021-03-25 15:08:57 -07:00
Jamie Vigliotta
e2b4f0a3fa updated comments and reorganized telemetry collection class a bit 2021-03-24 13:37:02 -07:00
Jamie Vigliotta
6f952789ed WIP 2021-03-22 12:24:27 -07:00
Jamie Vigliotta
2bf06dbf23 mergeing master 2021-03-19 10:28:54 -07:00
Jamie V
b02bd08a12 Merge branch 'master' into telemetry-collection 2021-02-19 12:00:01 -08:00
Jamie Vigliotta
a9b70c6f9a reworking telemetry collections to utilize collectionStates for state tracking 2021-02-16 15:56:06 -08:00
Jamie Vigliotta
c09fc6110b Merge branch 'master' into telemetry-collection
Merge'n master
2021-02-16 12:25:04 -08:00
Jamie Vigliotta
84e45e2543 Merge branch 'master' into telemetry-collection
Merge'n master
2021-02-12 11:42:22 -08:00
Jamie Vigliotta
8f50425043 Minor updates after meeting 2021-01-13 09:06:16 -08:00
Jamie Vigliotta
f7f1b0d97e removed unnecessary subscription service, just reusing from telemetry api 2021-01-07 09:46:24 -08:00
Jamie Vigliotta
cb08a82a5d WIP: reusing subscribe from telemetry api 2021-01-07 09:40:36 -08:00
Jamie Vigliotta
d9caa734d3 Merge branch 'master' into telemetry-collection
Merg'n master
2021-01-05 11:24:06 -08:00
Jamie Vigliotta
72848849dd WIP working with imagery for testing 2020-12-14 14:14:47 -08:00
Jamie Vigliotta
66130ba542 Merge branch 'master' into telemetry-collection
Merg'n master
2020-12-11 10:49:12 -08:00
Jamie Vigliotta
895bdc164f WIP 2020-12-09 11:57:11 -08:00
Jamie Vigliotta
c9728144a5 Merge branch 'master' into telemetry-collection
Merg'n master
2020-12-07 12:12:21 -08:00
Jamie Vigliotta
76fec7f3bc WIP 2020-12-01 12:04:14 -08:00
Jamie Vigliotta
1b4717065a WIP 2020-11-30 14:35:02 -08:00
Jamie Vigliotta
e24542c1a6 Merge branch 'master' into telemetry-collection
Merg'n master
2020-11-24 11:22:04 -08:00
Jamie Vigliotta
b08f3106ed WIP 2020-11-24 10:34:08 -08:00
Jamie Vigliotta
700bc7616d WIP 2020-11-20 11:13:41 -08:00
12 changed files with 1168 additions and 718 deletions

View File

@@ -63,7 +63,7 @@ define([
StateGeneratorProvider.prototype.request = function (domainObject, options) {
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;
if (options.strategy === 'latest' || options.size === 1) {
start = end;

View File

@@ -20,6 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { TelemetryCollection } = require("./TelemetryCollection");
define([
'../../plugins/displayLayout/CustomStringFormatter',
'./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.
* The `options` argument allows you to specify filters

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,449 @@
/*****************************************************************************
* 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';
/**
* Binds class methods
*/
function methods() {
return [
'load',
'on',
'off',
'hasMorePages',
'nextPage',
'updateOptions',
'destroy',
'_requestHistoricalTelemetry',
'_initiateHistoricalRequests',
'_initiateSubscriptionTelemetry',
'_addPage',
'_processNewTelemetry',
'_bounds',
'_timeSystem',
'_reset',
'_emit',
'_watchBounds',
'_unwatchBounds',
'_watchTimeSystem',
'_unwatchTimeSystem'
];
}
/** Class representing a Telemetry Collection. */
export class TelemetryCollection {
/**
* 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) {
methods().forEach(method => this[method] = this[method].bind(this));
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.collectionState = undefined;
this.listeners = {
add: [],
remove: [],
timesystem: [],
bounds: []
};
}
/**
* This will load 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;
}
/**
* returns if there is more telemetry within the time bounds
* if the provider supports it
*
* @returns {boolean}
*/
hasMorePages() {
return this.historicalProvider
&& this.historicalProvider.supportsPaging
&& this.historicalProvider.supportsPaging()
&& this.historicalProvider.hasMorePages
&& this.historicalProvider.hasMorePages(this);
}
/**
* will trigger the next page for the provider if it supports it,
* _addPage will be passed in as a callback to receive the telemetry and updated state
*/
nextPage() {
if (
!this.historicalProvider
|| !this.historicalProvider.supportsPaging()
|| !this.historicalProvider.nextPage
) {
throw new Error('Provider does not support paging');
}
this.historicalProvider.nextPage(this._addPage, this.collectionState);
}
/**
* provides a way to update the options for the telemetry request and subscription
* doing so will trigger a lite reset (no re-reqeustiong data yet) and then a
* re-initialization of request and subscription
*
* @param {Object} options - options to send into request/subscription providers
*/
updateOptions(options) {
const SKIP_RESET_REQUEST = true;
this._reset(SKIP_RESET_REQUEST);
this.options = options;
// will update options and providers if necesarry
this._initiateHistoricalRequests();
this._initiateSubscriptionTelemetry();
}
/**
* @param {string} event - add, remove
* @param {requestCallback} callback - callback to be executed when event happens,
* should accept an array of added telemetry data
* @param {object} [context] - optional context to use
*/
on(event, callback, context) {
if (!this.listeners[event]) {
throw new Error('Event not supported by Telemetry Collections: ' + event);
}
if (this.listeners[event].includes(callback)) {
throw new Error('Tried to add a listener that is already registered');
}
this.listeners[event].push({
callback,
context
});
}
/**
* @param {string} event - add, remove
* @param {requestCallback} callback - callback to be executed when event happens,
* should accept an array of removed
* telemetry data
*/
off(event, callback) {
if (!this.listeners[event]) {
throw new Error('Event not supported by Telemetry Collections: ' + event);
}
if (!this.listeners[event].includes(callback)) {
throw new Error('Tried to remove a listener that does not exist');
}
this.listeners[event].remove(callback);
}
/**
* can/should be called by the requester of the telemetry collection
* to remove any listeners
*/
destroy() {
this._unwatchBounds();
this._unwatchTimeSystem();
if (this.unsubscribe) {
this.unsubscribe();
}
}
/**
* Sets up the telemetry collection for historical requests,
* this uses the "standardizeRequestOptions" from Telemetry API
*/
_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
*/
async _requestHistoricalTelemetry() {
if (!this.historicalProvider) {
return;
}
let historicalData = await this.historicalProvider.request(this.domainObject, this.options)
.catch((rejected) => {
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
console.error(rejected);
return Promise.reject(rejected);
});
if (Array.isArray(historicalData)) {
this._processNewTelemetry(historicalData);
}
}
/**
* This uses the built in subscription function from Telemetry API
*/
_initiateSubscriptionTelemetry() {
if (this.unsubscribe) {
this.unsubscribe();
}
this.unsubscribe = this.openmct.telemetry
.subscribe(this.domainObject, (datum) => {
this._processNewTelemetry(datum);
}, this.options);
}
/**
* call back for telemetry provider to add more data as well as
* pass in the current state of the telemetry collection
* (which the telemetry collection will hold)
*
*
* @param {Object[]} telemetryData - array of telemetry data objects
* @param {*} [collectionState] - providers can pass a collectionState that
* will be used for tracking between collection and provider
*/
_addPage(telemetryData, collectionState) {
this.collectionState = collectionState;
this._processNewTelemetry(telemetryData);
}
/**
* Filter any new telemetry (add/page, historical, subscription) based on
* time bounds
*
* @param {(Object|Object[])} telemetryData - telemetry data object or
* array of telemetry data objects
*/
_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) {
if (!this.boundedTelemetry.includes(datum)) {
this.boundedTelemetry.push(datum);
added.push(datum);
}
} else if (afterEndOfBounds) {
this.futureBuffer.push(datum);
}
}
if (added.length) {
this._emit('add', added);
}
}
/**
* when the start time, end time, or both have been updated.
* data could be added OR removed here we update the current
* bounded telemetry and emit the results
*
* @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.
*/
_bounds(bounds, isTick) {
let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end;
this.lastBounds = bounds;
this._emit('bounds', ...arguments);
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
*/
_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._emit('timesystem', timeSystem);
this._reset();
}
/**
* Reset the telemetry data of the collection, and re-request
* historical telemetry
*
* @param {boolean} skipRequest - skip requesting history, default false
*
* @todo handle subscriptions more granually
*/
_reset(skipRequest = false) {
this.boundedTelemetry = [];
this.futureBuffer = [];
if (skipRequest) {
return;
}
this._requestHistoricalTelemetry();
// possible unsubscribe/resubscribe...
}
/**
* will call all the listeners for the event type and pass in the args
*
* @param {string} event event type, 'add' or 'remove'
* @param {arguments} args arguments to pass to the cvent callback
*/
_emit(event, ...args) {
if (!this.listeners[event].length) {
return;
}
this.listeners[event].forEach((listener) => {
if (listener.context) {
listener.callback.apply(listener.context, args);
} else {
listener.callback(...args);
}
});
}
/**
* adds the _bounds callback to the 'bounds' timeAPI listener
*/
_watchBounds() {
this.openmct.time.on('bounds', this._bounds);
}
/**
* removes the _bounds callback from the 'bounds' timeAPI listener
*/
_unwatchBounds() {
this.openmct.time.off('bounds', this._bounds);
}
/**
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
*/
_watchTimeSystem() {
this.openmct.time.on('timeSystem', this._timeSystem);
}
/**
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
*/
_unwatchTimeSystem() {
this.openmct.time.off('timeSystem', this._timeSystem);
}
}

View File

@@ -23,7 +23,7 @@
define([
'EventEmitter',
'lodash',
'./collections/BoundedTableRowCollection',
'./collections/SortedTableRowCollection',
'./collections/FilteredTableRowCollection',
'./TelemetryTableNameColumn',
'./TelemetryTableRow',
@@ -33,7 +33,7 @@ define([
], function (
EventEmitter,
_,
BoundedTableRowCollection,
SortedTableRowCollection,
FilteredTableRowCollection,
TelemetryTableNameColumn,
TelemetryTableRow,
@@ -48,20 +48,21 @@ define([
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = 100;
this.subscriptions = {};
this.tableComposition = undefined;
this.telemetryObjects = [];
this.datumCache = [];
this.removeDatumCache = [];
this.outstandingRequests = 0;
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.telemetryCollections = {};
this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.refreshData = this.refreshData.bind(this);
this.requestDataFor = this.requestDataFor.bind(this);
this.requestTelemetryCollectionFor = this.requestTelemetryCollectionFor.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
@@ -101,8 +102,8 @@ define([
}
createTableRowCollections() {
this.boundedRows = new BoundedTableRowCollection(this.openmct);
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
this.sortedRows = new SortedTableRowCollection(this.openmct);
this.filteredRows = new FilteredTableRowCollection(this.sortedRows);
//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;
@@ -115,24 +116,23 @@ define([
this.filteredRows.sortBy(sortOptions);
}
loadComposition() {
async loadComposition() {
this.tableComposition = this.openmct.composition.get(this.domainObject);
if (this.tableComposition !== undefined) {
this.tableComposition.load().then((composition) => {
let composition = await this.tableComposition.load();
composition = composition.filter(this.isTelemetryObject);
composition.forEach(this.addTelemetryObject);
composition = composition.filter(this.isTelemetryObject);
composition.forEach(this.addTelemetryObject);
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
});
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
}
}
addTelemetryObject(telemetryObject) {
this.addColumnsForObject(telemetryObject, true);
this.requestDataFor(telemetryObject);
this.subscribeTo(telemetryObject);
this.requestTelemetryCollectionFor(telemetryObject);
this.telemetryObjects.push(telemetryObject);
this.emit('object-added', telemetryObject);
@@ -140,46 +140,107 @@ define([
updateFilters() {
this.filteredRows.clear();
this.boundedRows.clear();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.sortedRows.clear();
this.telemetryObjects.forEach(this.requestDataFor.bind(this));
this.telemetryObjects.forEach(this.subscribeTo.bind(this));
this.telemetryObjects.forEach(this.requestTelemetryCollectionFor.bind(this));
}
removeTelemetryObject(objectIdentifier) {
this.configuration.removeColumnsForObject(objectIdentifier, true);
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.boundedRows.removeAllRowsForObject(keyString);
this.unsubscribe(keyString);
this.sortedRows.removeAllRowsForObject(keyString);
this.removeTelemetryCollection(keyString);
this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(objectIdentifier, object.identifier));
this.emit('object-removed', objectIdentifier);
}
requestDataFor(telemetryObject) {
requestTelemetryCollectionFor(telemetryObject) {
this.incrementOutstandingRequests();
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
const telemetryProcessor = this.getTelemetryProcessor(telemetryObject, keyString);
const telemetryRemover = this.getTelemetryRemover(keyString);
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;
}
this.removeTelemetryCollection(keyString);
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();
});
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();
}
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
removeTelemetryCollection(keyString) {
if (this.telemetryCollections[keyString]) {
this.telemetryCollections[keyString].destroy();
this.telemetryCollections[keyString] = undefined;
delete this.telemetryCollections[keyString];
}
}
getTelemetryProcessor(telemetryObject, keyString) {
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
return (telemetry) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
// only cache realtime
if (this.paused) {
let cacheData = {
telemetry,
columnMap,
keyString,
limitEvaluator
};
this.datumCache.push(cacheData);
} else {
this.processTelemetryData(telemetry, columnMap, keyString, limitEvaluator);
}
};
}
getTelemetryRemover(keyString) {
return (telemetry) => {
// only cache realtime
if (this.paused) {
let cacheData = {
telemetry,
keyString
};
this.removeDatumCache.push(cacheData);
} else {
this.sortedRows.removeRowsFor(telemetry, keyString);
}
};
}
processTelemetryData(telemetryData, columnMap, keyString, limitEvaluator) {
let timeKey = this.openmct.time.timeSystem().key;
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator, timeKey));
this.sortedRows.add(telemetryRows);
}
processDatumCache() {
this.datumCache.forEach(cachedDatum => {
this.processTelemetryData(cachedDatum.telemetry, cachedDatum.columnMap, cachedDatum.keyString, cachedDatum.limitEvaluator);
});
this.datumCache = [];
this.removeDatumCache.forEach(cachedDatum => {
this.sortedRows.removeRowsFor(cachedDatum.telemetry, cachedDatum.keyString);
});
this.removeDatumCache = [];
}
/**
@@ -207,15 +268,15 @@ define([
refreshData(bounds, isTick) {
if (!isTick && this.outstandingRequests === 0) {
this.filteredRows.clear();
this.boundedRows.clear();
this.boundedRows.sortByTimeSystem(this.openmct.time.timeSystem());
this.telemetryObjects.forEach(this.requestDataFor);
this.sortedRows.clear();
this.sortedRows.sortByTimeSystem(this.openmct.time.timeSystem());
this.telemetryObjects.forEach(this.requestTelemetryCollectionFor);
}
}
clearData() {
this.filteredRows.clear();
this.boundedRows.clear();
this.sortedRows.clear();
this.emit('refresh');
}
@@ -256,44 +317,6 @@ define([
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
subscribeTo(telemetryObject) {
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) => {
//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');
}
@@ -307,11 +330,6 @@ define([
return {filters} || {};
}
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
}
sortBy(sortOptions) {
this.filteredRows.sortBy(sortOptions);
@@ -324,19 +342,18 @@ define([
pause() {
this.paused = true;
this.boundedRows.unsubscribeFromBounds();
}
unpause() {
this.paused = false;
this.processDatumCache();
this.boundedRows.subscribeToBounds();
}
destroy() {
this.boundedRows.destroy();
let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection);
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('timeSystem', this.refreshData);

View File

@@ -22,13 +22,14 @@
define([], function () {
class TelemetryTableRow {
constructor(datum, columns, objectKeyString, limitEvaluator) {
constructor(datum, columns, objectKeyString, limitEvaluator, timeKey) {
this.columns = columns;
this.datum = createNormalizedDatum(datum, columns);
this.fullDatum = datum;
this.limitEvaluator = limitEvaluator;
this.objectKeyString = objectKeyString;
this.rowId = objectKeyString + this.getParsedValue(timeKey);
}
getFormattedDatum(headers) {

View File

@@ -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;
});

View File

@@ -29,7 +29,7 @@ define(
) {
class FilteredTableRowCollection extends SortedTableRowCollection {
constructor(masterCollection) {
super();
super(masterCollection.openmct);
this.masterCollection = masterCollection;
this.columnFilters = {};

View File

@@ -37,12 +37,17 @@ define(
* @constructor
*/
class SortedTableRowCollection extends EventEmitter {
constructor() {
constructor(openmct) {
super();
this.openmct = openmct;
this.dupeCheck = false;
this.rows = [];
this.sortByTimeSystem = this.sortByTimeSystem.bind(this);
this.sortByTimeSystem(openmct.time.timeSystem());
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
}
@@ -162,6 +167,19 @@ define(
}
}
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);
}
/**
* Sorts the telemetry collection based on the provided sort field
* specifier. Subsequent inserts are sorted to maintain specified sport
@@ -227,8 +245,22 @@ define(
this.emit('remove', removed);
}
removeRowsFor(telemetryData, keystring) {
let discarded = [];
const rowIdMap = telemetryData.map((datum) => {
return keystring + this.parseTime(datum);
});
rowIdMap.forEach(id => {
const index = this.rows.findIndex(row => row.rowId === id);
discarded = [...discarded, ...this.rows.splice(index, 1)];
});
this.emit('remove', discarded);
}
getValueForSortColumn(row) {
return row.getParsedValue(this.sortOptions.key);
return this.parseTime(row.datum[this.sortOptions.key]);
}
remove(removedRows) {
@@ -243,6 +275,10 @@ define(
return this.rows;
}
getRowsLength() {
return this.rows.length;
}
clear() {
let removedRows = this.rows;
this.rows = [];

View File

@@ -471,7 +471,6 @@ export default {
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
this.table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
@@ -655,7 +654,7 @@ export default {
* Calculates height based on total number of rows, and sets table height.
*/
setHeight() {
let filteredRowsLength = this.table.filteredRows.getRows().length;
let filteredRowsLength = this.table.filteredRows.getRowsLength();
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
// 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.

View File

@@ -48,6 +48,7 @@ describe("the plugin", () => {
let tablePlugin;
let element;
let child;
let historicalProvider;
beforeEach((done) => {
openmct = createOpenMct();
@@ -57,7 +58,12 @@ describe("the plugin", () => {
tablePlugin = new 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');
child = document.createElement('div');
@@ -166,11 +172,12 @@ describe("the plugin", () => {
let telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve;
});
openmct.telemetry.request.and.callFake(() => {
historicalProvider.request = () => {
telemetryPromiseResolve(testTelemetry);
return telemetryPromise;
});
};
openmct.router.path = [testTelemetryObject];

View File

@@ -35,10 +35,16 @@
</template>
<script>
// import moment from 'moment';
const DEFAULT_DURATION_FORMATTER = 'duration';
const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
const DEFAULT_RECORDS = 10;
const ONE_MINUTE = 60 * 1000;
const ONE_HOUR = ONE_MINUTE * 60;
const EIGHT_HOURS = 8 * ONE_HOUR;
const TWENTYFOUR_HOURS = EIGHT_HOURS * 3;
export default {
inject: ['openmct', 'configuration'],
@@ -135,10 +141,20 @@ export default {
methods: {
getHistoryMenuItems() {
const history = this.historyForCurrentTimeSystem.map(timespan => {
let name;
let startTime = this.formatTime(timespan.start);
let description = `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`;
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
name = `Starting ${startTime} ${this.getDuration(timespan.end - timespan.start)}`;
} else {
name = `${startTime} - ${this.formatTime(timespan.end)}`;
}
return {
cssClass: 'icon-history',
name: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
description: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
name,
description,
callBack: () => this.selectTimespan(timespan)
};
});
@@ -163,6 +179,23 @@ export default {
};
});
},
getDuration(numericDuration) {
let result;
let age;
if (numericDuration > TWENTYFOUR_HOURS) {
age = (numericDuration / TWENTYFOUR_HOURS).toFixed(2);
result = ` for ${age} days`;
} else if (numericDuration > ONE_HOUR) {
age = (numericDuration / ONE_HOUR).toFixed(2);
result = ` for ${age} hours`;
} else {
age = (numericDuration / ONE_MINUTE).toFixed(2);
result = ` for ${age} mins`;
}
return result;
},
getHistoryFromLocalStorage() {
const localStorageHistory = localStorage.getItem(this.storageKey);
const history = localStorageHistory ? JSON.parse(localStorageHistory) : undefined;