From 9e23f79bc8b1b8cc702be3ede097f4f860024190 Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Fri, 21 Oct 2022 20:25:24 +0200 Subject: [PATCH] Add time context to telemetry requests (#5887) * add time context to telemetry requests * change to empty array * refactor telemetry api to use time context * removed unused function * add tests * add test, rename function * make function public --- src/api/telemetry/TelemetryAPI.js | 86 +++---- src/api/telemetry/TelemetryAPISpec.js | 224 +++++++++--------- src/api/time/TimeAPISpec.js | 19 ++ src/api/time/TimeContext.js | 12 + src/plugins/LADTable/components/LADRow.vue | 5 +- .../components/TelemetryView.vue | 5 +- 6 files changed, 181 insertions(+), 170 deletions(-) diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 15cef5b191..c2a392d182 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -27,7 +27,6 @@ import TelemetryMetadataManager from './TelemetryMetadataManager'; import TelemetryValueFormatter from './TelemetryValueFormatter'; import DefaultMetadataProvider from './DefaultMetadataProvider'; import objectUtils from 'objectUtils'; -import _ from 'lodash'; export default class TelemetryAPI { @@ -73,7 +72,7 @@ export default class TelemetryAPI { * @returns {boolean} true if the object is a telemetry object. */ isTelemetryObject(domainObject) { - return Boolean(this.findMetadataProvider(domainObject)); + return Boolean(this.#findMetadataProvider(domainObject)); } /** @@ -87,7 +86,7 @@ export default class TelemetryAPI { * @memberof module:openmct.TelemetryAPI~TelemetryProvider# */ canProvideTelemetry(domainObject) { - return Boolean(this.findSubscriptionProvider(domainObject)) + return Boolean(this.#findSubscriptionProvider(domainObject)) || Boolean(this.findRequestProvider(domainObject)); } @@ -120,7 +119,7 @@ export default class TelemetryAPI { /** * @private */ - findSubscriptionProvider() { + #findSubscriptionProvider() { const args = Array.prototype.slice.apply(arguments); function supportsDomainObject(provider) { return provider.supportsSubscribe.apply(provider, args); @@ -130,9 +129,10 @@ export default class TelemetryAPI { } /** - * @private + * Returns a telemetry request provider that supports + * a given domain object and options. */ - findRequestProvider(domainObject) { + findRequestProvider() { const args = Array.prototype.slice.apply(arguments); function supportsDomainObject(provider) { return provider.supportsRequest.apply(provider, args); @@ -144,7 +144,7 @@ export default class TelemetryAPI { /** * @private */ - findMetadataProvider(domainObject) { + #findMetadataProvider(domainObject) { return this.metadataProviders.filter(function (p) { return p.supportsMetadata(domainObject); })[0]; @@ -153,7 +153,7 @@ export default class TelemetryAPI { /** * @private */ - findLimitEvaluator(domainObject) { + #findLimitEvaluator(domainObject) { return this.limitProviders.filter(function (p) { return p.supportsLimits(domainObject); })[0]; @@ -161,6 +161,7 @@ export default class TelemetryAPI { /** * @private + * Though used in TelemetryCollection as well */ standardizeRequestOptions(options) { if (!Object.prototype.hasOwnProperty.call(options, 'start')) { @@ -174,6 +175,10 @@ export default class TelemetryAPI { if (!Object.prototype.hasOwnProperty.call(options, 'domain')) { options.domain = this.openmct.time.timeSystem().key; } + + if (!Object.prototype.hasOwnProperty.call(options, 'timeContext')) { + options.timeContext = this.openmct.time; + } } /** @@ -241,7 +246,7 @@ export default class TelemetryAPI { /** * Request historical telemetry for a domain object. * The `options` argument allows you to specify filters - * (start, end, etc.), sort order, and strategies for retrieving + * (start, end, etc.), sort order, time context, and strategies for retrieving * telemetry (aggregation, latest available, etc.). * * @method request @@ -255,7 +260,7 @@ export default class TelemetryAPI { */ async request(domainObject) { if (this.noRequestProviderForAllObjects) { - return Promise.resolve([]); + return []; } if (arguments.length === 1) { @@ -273,22 +278,24 @@ export default class TelemetryAPI { if (!provider) { this.requestAbortControllers.delete(abortController); - return this.handleMissingRequestProvider(domainObject); + return this.#handleMissingRequestProvider(domainObject); } arguments[1] = await this.applyRequestInterceptors(domainObject, arguments[1]); + try { + const telemetry = await provider.request(...arguments); - return provider.request.apply(provider, arguments) - .catch((rejected) => { - if (rejected.name !== 'AbortError') { - this.openmct.notifications.error('Error requesting telemetry data, see console for details'); - console.error(rejected); - } + return telemetry; + } catch (error) { + if (error.name !== 'AbortError') { + this.openmct.notifications.error('Error requesting telemetry data, see console for details'); + console.error(error); + } - return Promise.reject(rejected); - }).finally(() => { - this.requestAbortControllers.delete(abortController); - }); + throw new Error(error); + } finally { + this.requestAbortControllers.delete(abortController); + } } /** @@ -306,7 +313,7 @@ export default class TelemetryAPI { * the subscription */ subscribe(domainObject, callback, options) { - const provider = this.findSubscriptionProvider(domainObject); + const provider = this.#findSubscriptionProvider(domainObject); if (!this.subscribeCache) { this.subscribeCache = {}; @@ -353,7 +360,7 @@ export default class TelemetryAPI { */ getMetadata(domainObject) { if (!this.metadataCache.has(domainObject)) { - const metadataProvider = this.findMetadataProvider(domainObject); + const metadataProvider = this.#findMetadataProvider(domainObject); if (!metadataProvider) { return; } @@ -369,33 +376,6 @@ export default class TelemetryAPI { return this.metadataCache.get(domainObject); } - /** - * Return an array of valueMetadatas that are common to all supplied - * telemetry objects and match the requested hints. - * - */ - commonValuesForHints(metadatas, hints) { - const options = metadatas.map(function (metadata) { - const values = metadata.valuesForHints(hints); - - return _.keyBy(values, 'key'); - }).reduce(function (a, b) { - const results = {}; - Object.keys(a).forEach(function (key) { - if (Object.prototype.hasOwnProperty.call(b, key)) { - results[key] = a[key]; - } - }); - - return results; - }); - const sortKeys = hints.map(function (h) { - return 'hints.' + h; - }); - - return _.sortBy(options, sortKeys); - } - /** * Get a value formatter for a given valueMetadata. * @@ -450,7 +430,7 @@ export default class TelemetryAPI { * * @returns Promise */ - handleMissingRequestProvider(domainObject) { + #handleMissingRequestProvider(domainObject) { this.noRequestProviderForAllObjects = this.requestProviders.every(requestProvider => { const supportsRequest = requestProvider.supportsRequest.apply(requestProvider, arguments); const hasRequestProvider = Object.prototype.hasOwnProperty.call(requestProvider, 'request') && typeof requestProvider.request === 'function'; @@ -540,7 +520,7 @@ export default class TelemetryAPI { * @memberof module:openmct.TelemetryAPI~TelemetryProvider# */ getLimitEvaluator(domainObject) { - const provider = this.findLimitEvaluator(domainObject); + const provider = this.#findLimitEvaluator(domainObject); if (!provider) { return { evaluate: function () {} @@ -578,7 +558,7 @@ export default class TelemetryAPI { * @memberof module:openmct.TelemetryAPI~TelemetryProvider# */ getLimits(domainObject) { - const provider = this.findLimitEvaluator(domainObject); + const provider = this.#findLimitEvaluator(domainObject); if (!provider || !provider.getLimits) { return { limits: function () { diff --git a/src/api/telemetry/TelemetryAPISpec.js b/src/api/telemetry/TelemetryAPISpec.js index 5af9440098..1362ec0ae5 100644 --- a/src/api/telemetry/TelemetryAPISpec.js +++ b/src/api/telemetry/TelemetryAPISpec.js @@ -23,11 +23,11 @@ import { createOpenMct, resetApplicationState } from 'utils/testing'; import TelemetryAPI from './TelemetryAPI'; import TelemetryCollection from './TelemetryCollection'; -describe('Telemetry API', function () { +describe('Telemetry API', () => { let openmct; let telemetryAPI; - beforeEach(function () { + beforeEach(() => { openmct = { time: jasmine.createSpyObj('timeAPI', [ 'timeSystem', @@ -47,11 +47,11 @@ describe('Telemetry API', function () { }); - describe('telemetry providers', function () { + describe('telemetry providers', () => { let telemetryProvider; let domainObject; - beforeEach(function () { + beforeEach(() => { telemetryProvider = jasmine.createSpyObj('telemetryProvider', [ 'supportsSubscribe', 'subscribe', @@ -73,19 +73,16 @@ describe('Telemetry API', function () { }; }); - it('provides consistent results without providers', function (done) { + it('provides consistent results without providers', async () => { const unsubscribe = telemetryAPI.subscribe(domainObject); expect(unsubscribe).toEqual(jasmine.any(Function)); - telemetryAPI.request(domainObject) - .then((data) => { - expect(data).toEqual([]); - }) - .finally(done); + const data = await telemetryAPI.request(domainObject); + expect(data).toEqual([]); }); - it('skips providers that do not match', function (done) { + it('skips providers that do not match', async () => { telemetryProvider.supportsSubscribe.and.returnValue(false); telemetryProvider.supportsRequest.and.returnValue(false); telemetryProvider.request.and.returnValue(Promise.resolve([])); @@ -98,14 +95,13 @@ describe('Telemetry API', function () { expect(telemetryProvider.subscribe).not.toHaveBeenCalled(); expect(unsubscribe).toEqual(jasmine.any(Function)); - telemetryAPI.request(domainObject).then((response) => { - expect(telemetryProvider.supportsRequest) - .toHaveBeenCalledWith(domainObject, jasmine.any(Object)); - expect(telemetryProvider.request).not.toHaveBeenCalled(); - }).finally(done); + await telemetryAPI.request(domainObject); + expect(telemetryProvider.supportsRequest) + .toHaveBeenCalledWith(domainObject, jasmine.any(Object)); + expect(telemetryProvider.request).not.toHaveBeenCalled(); }); - it('sends subscribe calls to matching providers', function () { + it('sends subscribe calls to matching providers', () => { const unsubFunc = jasmine.createSpy('unsubscribe'); telemetryProvider.subscribe.and.returnValue(unsubFunc); telemetryProvider.supportsSubscribe.and.returnValue(true); @@ -133,7 +129,7 @@ describe('Telemetry API', function () { expect(callback).not.toHaveBeenCalledWith('otherValue'); }); - it('subscribes once per object', function () { + it('subscribes once per object', () => { const unsubFunc = jasmine.createSpy('unsubscribe'); telemetryProvider.subscribe.and.returnValue(unsubFunc); telemetryProvider.supportsSubscribe.and.returnValue(true); @@ -164,7 +160,7 @@ describe('Telemetry API', function () { expect(callbacktwo).not.toHaveBeenCalledWith('anotherValue'); }); - it('only deletes subscription cache when there are no more subscribers', function () { + it('only deletes subscription cache when there are no more subscribers', () => { const unsubFunc = jasmine.createSpy('unsubscribe'); telemetryProvider.subscribe.and.returnValue(unsubFunc); telemetryProvider.supportsSubscribe.and.returnValue(true); @@ -187,7 +183,7 @@ describe('Telemetry API', function () { unsubscribeThree(); }); - it('does subscribe/unsubscribe', function () { + it('does subscribe/unsubscribe', () => { const unsubFunc = jasmine.createSpy('unsubscribe'); telemetryProvider.subscribe.and.returnValue(unsubFunc); telemetryProvider.supportsSubscribe.and.returnValue(true); @@ -203,7 +199,7 @@ describe('Telemetry API', function () { unsubscribe(); }); - it('subscribes for different object', function () { + it('subscribes for different object', () => { const unsubFuncs = []; const notifiers = []; telemetryProvider.supportsSubscribe.and.returnValue(true); @@ -243,120 +239,120 @@ describe('Telemetry API', function () { expect(unsubFuncs[1]).toHaveBeenCalled(); }); - it('sends requests to matching providers', function (done) { + it('sends requests to matching providers', async () => { const telemPromise = Promise.resolve([]); telemetryProvider.supportsRequest.and.returnValue(true); telemetryProvider.request.and.returnValue(telemPromise); telemetryAPI.addProvider(telemetryProvider); - telemetryAPI.request(domainObject).then(() => { - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( - domainObject, - jasmine.any(Object) - ); - expect(telemetryProvider.request).toHaveBeenCalledWith( - domainObject, - jasmine.any(Object) - ); - }).finally(done); + await telemetryAPI.request(domainObject); + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( + domainObject, + jasmine.any(Object) + ); + expect(telemetryProvider.request).toHaveBeenCalledWith( + domainObject, + jasmine.any(Object) + ); }); - it('generates default request options', function (done) { + it('generates default request options', async () => { telemetryProvider.supportsRequest.and.returnValue(true); telemetryProvider.request.and.returnValue(Promise.resolve([])); telemetryAPI.addProvider(telemetryProvider); - telemetryAPI.request(domainObject).then(() => { - const { signal } = new AbortController(); - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( - jasmine.any(Object), - { - signal, - start: 0, - end: 1, - domain: 'system' - } - ); + await telemetryAPI.request(domainObject); + const { signal } = new AbortController(); + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( + jasmine.any(Object), + { + signal, + start: 0, + end: 1, + domain: 'system', + timeContext: jasmine.any(Object) + } + ); - expect(telemetryProvider.request).toHaveBeenCalledWith( - jasmine.any(Object), - { - signal, - start: 0, - end: 1, - domain: 'system' - } - ); + expect(telemetryProvider.request).toHaveBeenCalledWith( + jasmine.any(Object), + { + signal, + start: 0, + end: 1, + domain: 'system', + timeContext: jasmine.any(Object) + } + ); - telemetryProvider.supportsRequest.calls.reset(); - telemetryProvider.request.calls.reset(); + telemetryProvider.supportsRequest.calls.reset(); + telemetryProvider.request.calls.reset(); - telemetryAPI.request(domainObject, {}).then(() => { - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( - jasmine.any(Object), - { - signal, - start: 0, - end: 1, - domain: 'system' - } - ); - - expect(telemetryProvider.request).toHaveBeenCalledWith( - jasmine.any(Object), - { - signal, - start: 0, - end: 1, - domain: 'system' - } - ); - }); - }).finally(done); + await telemetryAPI.request(domainObject, {}); + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( + jasmine.any(Object), + { + signal, + start: 0, + end: 1, + domain: 'system', + timeContext: jasmine.any(Object) + } + ); + expect(telemetryProvider.request).toHaveBeenCalledWith( + jasmine.any(Object), + { + signal, + start: 0, + end: 1, + domain: 'system', + timeContext: jasmine.any(Object) + } + ); }); - it('do not overwrite existing request options', function (done) { + it('do not overwrite existing request options', async () => { telemetryProvider.supportsRequest.and.returnValue(true); telemetryProvider.request.and.returnValue(Promise.resolve([])); telemetryAPI.addProvider(telemetryProvider); - telemetryAPI.request(domainObject, { + await telemetryAPI.request(domainObject, { start: 20, end: 30, domain: 'someDomain' - }).then(() => { - const { signal } = new AbortController(); - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( - jasmine.any(Object), - { - start: 20, - end: 30, - domain: 'someDomain', - signal - } - ); + }); + const { signal } = new AbortController(); + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith( + jasmine.any(Object), + { + start: 20, + end: 30, + domain: 'someDomain', + signal, + timeContext: jasmine.any(Object) + } + ); - expect(telemetryProvider.request).toHaveBeenCalledWith( - jasmine.any(Object), - { - start: 20, - end: 30, - domain: 'someDomain', - signal - } - ); - - }).finally(done); + expect(telemetryProvider.request).toHaveBeenCalledWith( + jasmine.any(Object), + { + start: 20, + end: 30, + domain: 'someDomain', + signal, + timeContext: jasmine.any(Object) + } + ); }); }); - describe('metadata', function () { + describe('metadata', () => { let mockMetadata = {}; let mockObjectType = { definition: {} }; - beforeEach(function () { + beforeEach(() => { telemetryAPI.addProvider({ key: 'mockMetadataProvider', supportsMetadata() { @@ -369,7 +365,7 @@ describe('Telemetry API', function () { openmct.types.get.and.returnValue(mockObjectType); }); - it('respects explicit priority', function () { + it('respects explicit priority', () => { mockMetadata.values = [ { key: "name", @@ -408,7 +404,7 @@ describe('Telemetry API', function () { expect(value.hints.priority).toBe(index + 1); }); }); - it('if no explicit priority, defaults to order defined', function () { + it('if no explicit priority, defaults to order defined', () => { mockMetadata.values = [ { key: "name", @@ -435,7 +431,7 @@ describe('Telemetry API', function () { expect(value.key).toBe(mockMetadata.values[index].key); }); }); - it('respects domain priority', function () { + it('respects domain priority', () => { mockMetadata.values = [ { key: "name", @@ -477,7 +473,7 @@ describe('Telemetry API', function () { expect(values[0].key).toBe('timestamp-local'); expect(values[1].key).toBe('timestamp-utc'); }); - it('respects range priority', function () { + it('respects range priority', () => { mockMetadata.values = [ { key: "name", @@ -519,7 +515,7 @@ describe('Telemetry API', function () { expect(values[0].key).toBe('cos'); expect(values[1].key).toBe('sin'); }); - it('respects priority and domain ordering', function () { + it('respects priority and domain ordering', () => { mockMetadata.values = [ { key: "id", @@ -588,7 +584,7 @@ describe('Telemetry API', function () { definition: {} }; - beforeEach(function () { + beforeEach(() => { openmct.telemetry = telemetryAPI; telemetryAPI.addProvider({ key: 'mockMetadataProvider', @@ -644,16 +640,14 @@ describe('Telemetery', () => { return resetApplicationState(openmct); }); - it('should not abort request without navigation', function (done) { + it('should not abort request without navigation', async () => { telemetryAPI.addProvider(telemetryProvider); - telemetryAPI.request({}).finally(() => { - expect(watchedSignal.aborted).toBe(false); - done(); - }); + await telemetryAPI.request({}); + expect(watchedSignal.aborted).toBe(false); }); - it('should abort request on navigation', function (done) { + it('should abort request on navigation', (done) => { telemetryAPI.addProvider(telemetryProvider); telemetryAPI.request({}).finally(() => { diff --git a/src/api/time/TimeAPISpec.js b/src/api/time/TimeAPISpec.js index c7dea10816..5108cf4490 100644 --- a/src/api/time/TimeAPISpec.js +++ b/src/api/time/TimeAPISpec.js @@ -229,6 +229,25 @@ describe("The Time API", function () { expect(api.clock()).toBeUndefined(); }); + it('Provides a default time context', () => { + const timeContext = api.getContextForView([]); + expect(timeContext).not.toBe(null); + }); + + it("Without a clock, is in fixed time mode", () => { + const timeContext = api.getContextForView([]); + expect(timeContext.isRealTime()).toBe(false); + }); + + it("Provided a clock, is in real-time mode", () => { + const timeContext = api.getContextForView([]); + timeContext.clock('mts', { + start: 0, + end: 1 + }); + expect(timeContext.isRealTime()).toBe(true); + }); + }); it("on tick, observes offsets, and indicates tick in bounds callback", function () { diff --git a/src/api/time/TimeContext.js b/src/api/time/TimeContext.js index 29f95b72a8..520540b2b2 100644 --- a/src/api/time/TimeContext.js +++ b/src/api/time/TimeContext.js @@ -362,6 +362,18 @@ class TimeContext extends EventEmitter { this.boundsVal = newBounds; this.emit('bounds', this.boundsVal, true); } + + /** + * Checks if this time context is in real-time mode or not. + * @returns {boolean} true if this context is in real-time mode, false if not + */ + isRealTime() { + if (this.clock()) { + return true; + } + + return false; + } } export default TimeContext; diff --git a/src/plugins/LADTable/components/LADRow.vue b/src/plugins/LADTable/components/LADRow.vue index 929a4e201c..3468c870b6 100644 --- a/src/plugins/LADTable/components/LADRow.vue +++ b/src/plugins/LADTable/components/LADRow.vue @@ -114,6 +114,8 @@ export default { this.formats = this.openmct.telemetry.getFormatMap(this.metadata); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); + this.timeContext = this.openmct.time.getContextForView(this.objectPath); + this.limitEvaluator = this.openmct .telemetry .limitEvaluator(this.domainObject); @@ -134,7 +136,8 @@ export default { this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { size: 1, - strategy: 'latest' + strategy: 'latest', + timeContext: this.timeContext }); this.telemetryCollection.on('add', this.setLatestValues); this.telemetryCollection.on('clear', this.resetValues); diff --git a/src/plugins/displayLayout/components/TelemetryView.vue b/src/plugins/displayLayout/components/TelemetryView.vue index 8ae30d9130..24bd7bce0e 100644 --- a/src/plugins/displayLayout/components/TelemetryView.vue +++ b/src/plugins/displayLayout/components/TelemetryView.vue @@ -282,12 +282,15 @@ export default { this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject); this.formats = this.openmct.telemetry.getFormatMap(this.metadata); + this.timeContext = this.openmct.time.getContextForView(this.objectPath); + const valueMetadata = this.metadata ? this.metadata.value(this.item.value) : {}; this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format); this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { size: 1, - strategy: 'latest' + strategy: 'latest', + timeContext: this.timeContext }); this.telemetryCollection.on('add', this.setLatestValues); this.telemetryCollection.on('clear', this.refreshData);