From 39463c515fe63a3bed52d011e7806529c8a7a934 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Thu, 16 Mar 2023 19:12:11 -0700 Subject: [PATCH] [Greedy LAD] Add new functionality for Latest Available Data views (#6432) * adding greedyLAD logic to telemetry collections --------- Co-authored-by: Andrew Henry --- src/api/telemetry/TelemetryAPI.js | 28 ++++++++- src/api/telemetry/TelemetryCollection.js | 72 ++++++++++++++++++------ 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 7fd1f29d40..71a7457062 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -29,6 +29,7 @@ import DefaultMetadataProvider from './DefaultMetadataProvider'; import objectUtils from 'objectUtils'; export default class TelemetryAPI { + #isGreedyLAD; constructor(openmct) { this.openmct = openmct; @@ -44,8 +45,8 @@ export default class TelemetryAPI { this.requestProviders = []; this.subscriptionProviders = []; this.valueFormatterCache = new WeakMap(); - this.requestInterceptorRegistry = new TelemetryRequestInterceptorRegistry(); + this.#isGreedyLAD = true; } abortAllRequests() { @@ -226,6 +227,31 @@ export default class TelemetryAPI { return modifiedRequest; } + /** + * Get or set greedy LAD. For stategy "latest" telemetry in + * realtime mode the start bound will be ignored if true and + * there is no new data to replace the existing data. + * defaults to true + * + * To turn off greedy LAD: + * openmct.telemetry.greedyLAD(false); + * + * @method greedyLAD + * @returns {boolean} if greedyLAD is active or not + * @memberof module:openmct.TelemetryAPI# + */ + greedyLAD(isGreedy) { + if (arguments.length > 0) { + if (isGreedy !== true && isGreedy !== false) { + throw new Error('Error setting greedyLAD. Greedy LAD only accepts true or false values'); + } + + this.#isGreedyLAD = isGreedy; + } + + return this.#isGreedyLAD; + } + /** * Request telemetry collection for a domain object. * The `options` argument allows you to specify filters diff --git a/src/api/telemetry/TelemetryCollection.js b/src/api/telemetry/TelemetryCollection.js index 954334458f..a60cb26209 100644 --- a/src/api/telemetry/TelemetryCollection.js +++ b/src/api/telemetry/TelemetryCollection.js @@ -30,8 +30,8 @@ export default class TelemetryCollection extends EventEmitter { /** * Creates a Telemetry Collection * - * @param {object} openmct - Openm MCT - * @param {object} domainObject - Domain Object to user for telemetry collection + * @param {OpenMCT} openmct - Open MCT + * @param {module:openmct.DomainObject} domainObject - Domain Object to use for telemetry collection * @param {object} options - Any options passed in for request/subscribe */ constructor(openmct, domainObject, options) { @@ -50,6 +50,7 @@ export default class TelemetryCollection extends EventEmitter { this.lastBounds = undefined; this.requestAbort = undefined; this.isStrategyLatest = this.options.strategy === 'latest'; + this.dataOutsideTimeBounds = false; } /** @@ -184,6 +185,7 @@ export default class TelemetryCollection extends EventEmitter { let afterEndOfBounds; let added = []; let addedIndices = []; + let hasDataBeforeStartBound = false; // loop through, sort and dedupe for (let datum of data) { @@ -191,7 +193,7 @@ export default class TelemetryCollection extends EventEmitter { beforeStartOfBounds = parsedValue < this.lastBounds.start; afterEndOfBounds = parsedValue > this.lastBounds.end; - if (!afterEndOfBounds && !beforeStartOfBounds) { + if (!afterEndOfBounds && (!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))) { let isDuplicate = false; let startIndex = this._sortedIndex(datum); let endIndex = undefined; @@ -218,6 +220,10 @@ export default class TelemetryCollection extends EventEmitter { this.boundedTelemetry.splice(index, 0, datum); addedIndices.push(index); added.push(datum); + + if (!hasDataBeforeStartBound && beforeStartOfBounds) { + hasDataBeforeStartBound = true; + } } } else if (afterEndOfBounds) { @@ -226,12 +232,18 @@ export default class TelemetryCollection extends EventEmitter { } if (added.length) { - // if latest strategy is requested, we need to check if the value is the latest unmitted value + // if latest strategy is requested, we need to check if the value is the latest unemitted value if (this.isStrategyLatest) { this.boundedTelemetry = [this.boundedTelemetry[this.boundedTelemetry.length - 1]]; // if true, then this value has yet to be emitted if (this.boundedTelemetry[0] !== latestBoundedDatum) { + if (hasDataBeforeStartBound) { + this._handleDataOutsideBounds(); + } else { + this._handleDataInsideBounds(); + } + this.emit('add', this.boundedTelemetry); } } else { @@ -294,6 +306,17 @@ export default class TelemetryCollection extends EventEmitter { let added = []; let testDatum = {}; + 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); + } + if (startChanged) { testDatum[this.timeKey] = bounds.start; @@ -307,20 +330,19 @@ export default class TelemetryCollection extends EventEmitter { ); discarded = this.boundedTelemetry.splice(0, startIndex); } else if (this.parseTime(testDatum) > this.parseTime(this.boundedTelemetry[0])) { - discarded = this.boundedTelemetry; - this.boundedTelemetry = []; - } - } + // if greedyLAD is active and there is no new data to replace, don't discard + const isGreedyLAD = this.openmct.telemetry.greedyLAD(); + const shouldRemove = (!isGreedyLAD || (isGreedyLAD && added.length > 0)); - 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); + if (shouldRemove) { + discarded = this.boundedTelemetry; + this.boundedTelemetry = []; + // since it IS strategy latest, we can assume there will be at least 1 datum + // unless no data was returned in the first request, we need to account for that + } else if (this.boundedTelemetry.length === 1) { + this._handleDataOutsideBounds(); + } + } } if (discarded.length > 0) { @@ -331,6 +353,8 @@ export default class TelemetryCollection extends EventEmitter { if (!this.isStrategyLatest) { this.boundedTelemetry = [...this.boundedTelemetry, ...added]; } else { + this._handleDataInsideBounds(); + added = [added[added.length - 1]]; this.boundedTelemetry = added; } @@ -345,6 +369,20 @@ export default class TelemetryCollection extends EventEmitter { } + _handleDataInsideBounds() { + if (this.dataOutsideTimeBounds) { + this.dataOutsideTimeBounds = false; + this.emit('dataInsideTimeBounds'); + } + } + + _handleDataOutsideBounds() { + if (!this.dataOutsideTimeBounds) { + this.dataOutsideTimeBounds = true; + this.emit('dataOutsideTimeBounds'); + } + } + /** * whenever the time system is updated need to update related values in * the Telemetry Collection and reset the telemetry collection