From 5b1298f2217cd0923209461aeb9282c8d174bac3 Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Fri, 14 Jul 2023 17:09:05 -0700 Subject: [PATCH] Adds limits subscription to the Telemetry API (#6735) * Add subscription for limits for domain objects --------- Co-authored-by: John Hill Co-authored-by: Andrew Henry --- e2e/baseFixtures.js | 3 +- e2e/pluginFixtures.js | 3 +- src/api/telemetry/TelemetryAPI.js | 110 ++++++++++++++++--- src/plugins/plot/configuration/PlotSeries.js | 22 +++- 4 files changed, 121 insertions(+), 17 deletions(-) diff --git a/e2e/baseFixtures.js b/e2e/baseFixtures.js index dbb8412121..7f800d8427 100644 --- a/e2e/baseFixtures.js +++ b/e2e/baseFixtures.js @@ -29,7 +29,7 @@ */ const base = require('@playwright/test'); -const { expect } = base; +const { expect, request } = base; const fs = require('fs'); const path = require('path'); const { v4: uuid } = require('uuid'); @@ -179,4 +179,5 @@ exports.test = base.test.extend({ }); exports.expect = expect; +exports.request = request; exports.waitForAnimations = waitForAnimations; diff --git a/e2e/pluginFixtures.js b/e2e/pluginFixtures.js index e30b5d8af4..f5c2de42aa 100644 --- a/e2e/pluginFixtures.js +++ b/e2e/pluginFixtures.js @@ -26,7 +26,7 @@ * and appActions. These fixtures should be generalized across all plugins. */ -const { test, expect } = require('./baseFixtures'); +const { test, expect, request } = require('./baseFixtures'); // const { createDomainObjectWithDefaults } = require('./appActions'); const path = require('path'); @@ -147,6 +147,7 @@ exports.test = test.extend({ } }); exports.expect = expect; +exports.request = request; /** * Takes a readable stream and returns a string. diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 6ad0cbcf76..fe15d3e892 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -489,6 +489,62 @@ export default class TelemetryAPI { }.bind(this); } + /** + * Subscribe to run-time changes in configured telemetry limits for a specific domain object. + * The callback will be called whenever data is received from a + * limit provider. + * + * @method subscribeToLimits + * @memberof module:openmct.TelemetryAPI~TelemetryProvider# + * @param {module:openmct.DomainObject} domainObject the object + * which has associated limits + * @param {Function} callback the callback to invoke with new data, as + * it becomes available + * @returns {Function} a function which may be called to terminate + * the subscription + */ + subscribeToLimits(domainObject, callback) { + if (domainObject.type === 'unknown') { + return () => {}; + } + + const provider = this.#findLimitEvaluator(domainObject); + + if (!this.limitsSubscribeCache) { + this.limitsSubscribeCache = {}; + } + + const keyString = objectUtils.makeKeyString(domainObject.identifier); + let subscriber = this.limitsSubscribeCache[keyString]; + + if (!subscriber) { + subscriber = this.limitsSubscribeCache[keyString] = { + callbacks: [callback] + }; + if (provider && provider.subscribeToLimits) { + subscriber.unsubscribe = provider.subscribeToLimits(domainObject, function (value) { + subscriber.callbacks.forEach(function (cb) { + cb(value); + }); + }); + } else { + subscriber.unsubscribe = function () {}; + } + } else { + subscriber.callbacks.push(callback); + } + + return function unsubscribe() { + subscriber.callbacks = subscriber.callbacks.filter(function (cb) { + return cb !== callback; + }); + if (subscriber.callbacks.length === 0) { + subscriber.unsubscribe(); + delete this.limitsSubscribeCache[keyString]; + } + }.bind(this); + } + /** * Request telemetry staleness for a domain object. * @@ -676,7 +732,7 @@ export default class TelemetryAPI { * * @param {module:openmct.DomainObject} domainObject the domain * object for which to get limits - * @returns {module:openmct.TelemetryAPI~LimitEvaluator} + * @returns {LimitsResponseObject} * @method limits * @memberof module:openmct.TelemetryAPI~TelemetryProvider# */ @@ -723,18 +779,8 @@ export default class TelemetryAPI { * * @param {module:openmct.DomainObject} domainObject the domain * object for which to display limits - * @returns {module:openmct.TelemetryAPI~LimitEvaluator} - * @method limits returns a limits object of - * type { - * level1: { - * low: { key1: value1, key2: value2, color: }, - * high: { key1: value1, key2: value2, color: } - * }, - * level2: { - * low: { key1: value1, key2: value2 }, - * high: { key1: value1, key2: value2 } - * } - * } + * @returns {LimitsResponseObject} + * @method limits returns a limits object of type {LimitsResponseObject} * supported colors are purple, red, orange, yellow and cyan * @memberof module:openmct.TelemetryAPI~TelemetryProvider# */ @@ -766,7 +812,7 @@ export default class TelemetryAPI { * @param {*} datum the telemetry datum to evaluate * @param {TelemetryProperty} the property to check for limit violations * @memberof module:openmct.TelemetryAPI~LimitEvaluator - * @returns {module:openmct.TelemetryAPI~LimitViolation} metadata about + * @returns {LimitViolation} metadata about * the limit violation, or undefined if a value is within limits */ @@ -777,6 +823,42 @@ export default class TelemetryAPI { * @property {string} cssClass the class (or space-separated classes) to * apply to display elements for values which violate this limit * @property {string} name the human-readable name for the limit violation + * @property {number} low a lower limit for violation + * @property {number} high a higher limit violation + */ + +/** + * @typedef {object} LimitsResponseObject + * @memberof {module:openmct.TelemetryAPI~} + * @property {LimitDefinition} limitLevel the level name and it's limit definition + * @example { + * [limitLevel]: { + * low: { + * color: lowColor, + * value: lowValue + * }, + * high: { + * color: highColor, + * value: highValue + * } + * } + * } + */ + +/** + * Limit defined for a telemetry property. + * @typedef LimitDefinition + * @memberof {module:openmct.TelemetryAPI~} + * @property {LimitDefinitionValue} low a lower limit + * @property {LimitDefinitionValue} high a higher limit + */ + +/** + * Limit definition for a Limit of a telemetry property. + * @typedef LimitDefinitionValue + * @memberof {module:openmct.TelemetryAPI~} + * @property {string} color color to represent this limit + * @property {Number} value the limit value */ /** diff --git a/src/plugins/plot/configuration/PlotSeries.js b/src/plugins/plot/configuration/PlotSeries.js index 4f3cd30a9e..13a916b100 100644 --- a/src/plugins/plot/configuration/PlotSeries.js +++ b/src/plugins/plot/configuration/PlotSeries.js @@ -141,6 +141,10 @@ export default class PlotSeries extends Model { this.unsubscribe(); } + if (this.unsubscribeLimits) { + this.unsubscribeLimits(); + } + if (this.removeMutationListener) { this.removeMutationListener(); } @@ -320,10 +324,26 @@ export default class PlotSeries extends Model { async load(options) { await this.fetch(options); this.emit('load'); + this.loadLimits(); + } + + async loadLimits() { const limitsResponse = await this.limitDefinition.limits(); - this.limits = []; + this.limits = {}; + if (!this.unsubscribeLimits) { + this.unsubscribeLimits = this.openmct.telemetry.subscribeToLimits( + this.domainObject, + this.limitsUpdated.bind(this) + ); + } + this.limitsUpdated(limitsResponse); + } + + limitsUpdated(limitsResponse) { if (limitsResponse) { this.limits = limitsResponse; + } else { + this.limits = {}; } this.emit('limits', this);