From 4415fe7952049023b5e4f0ec2fa3bde8ec1296b2 Mon Sep 17 00:00:00 2001 From: David Tsay <3614296+davetsay@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:10:35 -0700 Subject: [PATCH] Fix complex displays not loading (#7858) Clock * pass in default timeContext in request options * observe for clock tick, instead of polling, to determine if clock has time set Progress Bars * use scale instead of move animation Plan * use a ResizeObserver instead of polling for size changes --------- Co-authored-by: Jesse Mazzella Co-authored-by: Charles Hacskaylo Co-authored-by: Charles Hacskaylo Co-authored-by: Andrew Henry --- src/api/telemetry/TelemetryAPI.js | 28 +++++++---------- src/api/telemetry/TelemetryAPISpec.js | 30 +++++++++++-------- src/api/telemetry/TelemetryCollection.js | 8 ++--- src/plugins/plan/components/PlanView.vue | 7 +++-- src/plugins/remoteClock/RemoteClock.js | 21 +++++++------ src/plugins/remoteClock/requestInterceptor.js | 29 +++++++++++++++++- .../timeConductor/ConductorInputsRealtime.vue | 4 ++- src/ui/components/progress-bar.scss | 14 ++++----- 8 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index c9c83b195b..4268e64c36 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -231,26 +231,20 @@ export default class TelemetryAPI { * @returns {TelemetryRequestOptions} the options, with defaults filled in */ standardizeRequestOptions(options = {}) { - if (!Object.hasOwn(options, 'start')) { - const bounds = options.timeContext?.getBounds(); - if (bounds?.start) { - options.start = options.timeContext.getBounds().start; - } else { - options.start = this.openmct.time.getBounds().start; - } - } - - if (!Object.hasOwn(options, 'end')) { - const bounds = options.timeContext?.getBounds(); - if (bounds?.end) { - options.end = options.timeContext.getBounds().end; - } else { - options.end = this.openmct.time.getBounds().end; - } + if (!Object.hasOwn(options, 'timeContext')) { + options.timeContext = this.openmct.time; } if (!Object.hasOwn(options, 'domain')) { - options.domain = this.openmct.time.getTimeSystem().key; + options.domain = options.timeContext.getTimeSystem().key; + } + + if (!Object.hasOwn(options, 'start')) { + options.start = options.timeContext.getBounds().start; + } + + if (!Object.hasOwn(options, 'end')) { + options.end = options.timeContext.getBounds().end; } return options; diff --git a/src/api/telemetry/TelemetryAPISpec.js b/src/api/telemetry/TelemetryAPISpec.js index 03ea417501..648b1172ca 100644 --- a/src/api/telemetry/TelemetryAPISpec.js +++ b/src/api/telemetry/TelemetryAPISpec.js @@ -269,36 +269,40 @@ describe('Telemetry API', () => { await telemetryAPI.request(domainObject); const { signal } = new AbortController(); - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, { signal, start: 0, end: 1, - domain: 'system' + domain: 'system', + timeContext: openmct.time }); - expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, { signal, start: 0, end: 1, - domain: 'system' + domain: 'system', + timeContext: openmct.time }); telemetryProvider.supportsRequest.calls.reset(); telemetryProvider.request.calls.reset(); await telemetryAPI.request(domainObject, {}); - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, { signal, start: 0, end: 1, - domain: 'system' + domain: 'system', + timeContext: openmct.time }); - expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, { signal, start: 0, end: 1, - domain: 'system' + domain: 'system', + timeContext: openmct.time }); }); @@ -313,18 +317,20 @@ describe('Telemetry API', () => { domain: 'someDomain' }); const { signal } = new AbortController(); - expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, { start: 20, end: 30, domain: 'someDomain', - signal + signal, + timeContext: openmct.time }); - expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { + expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, { start: 20, end: 30, domain: 'someDomain', - signal + signal, + timeContext: openmct.time }); }); describe('telemetry batching support', () => { diff --git a/src/api/telemetry/TelemetryCollection.js b/src/api/telemetry/TelemetryCollection.js index d6f53816b9..602eb5ce87 100644 --- a/src/api/telemetry/TelemetryCollection.js +++ b/src/api/telemetry/TelemetryCollection.js @@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter { this.futureBuffer = []; this.parseTime = undefined; this.metadata = this.openmct.telemetry.getMetadata(domainObject); - if (!Object.hasOwn(options, 'timeContext')) { - options.timeContext = this.openmct.time; - } this.options = options; this.unsubscribe = undefined; this.pageState = undefined; @@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter { this._error(LOADED_ERROR); } + if (!Object.hasOwn(this.options, 'timeContext')) { + this.options.timeContext = this.openmct.time; + } this._setTimeSystem(this.options.timeContext.getTimeSystem()); this.lastBounds = this.options.timeContext.getBounds(); this._watchBounds(); @@ -127,7 +127,7 @@ export default class TelemetryCollection extends EventEmitter { * @private */ async _requestHistoricalTelemetry() { - let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options }); + const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options }); const historicalProvider = this.openmct.telemetry.findRequestProvider( this.domainObject, options diff --git a/src/plugins/plan/components/PlanView.vue b/src/plugins/plan/components/PlanView.vue index e3d6728268..86a9f3cdf1 100644 --- a/src/plugins/plan/components/PlanView.vue +++ b/src/plugins/plan/components/PlanView.vue @@ -69,7 +69,6 @@ const INNER_TEXT_PADDING = 15; const TEXT_LEFT_PADDING = 5; const ROW_PADDING = 5; const SWIMLANE_PADDING = 3; -const RESIZE_POLL_INTERVAL = 200; const ROW_HEIGHT = 22; const MAX_TEXT_WIDTH = 300; const MIN_ACTIVITY_WIDTH = 2; @@ -143,13 +142,15 @@ export default { this.canvasContext = canvas.getContext('2d'); this.setDimensions(); this.setTimeContext(); - this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL); this.handleConfigurationChange(this.configuration); this.planViewConfiguration.on('change', this.handleConfigurationChange); this.loadComposition(); + + this.resizeObserver = new ResizeObserver(this.resize); + this.resizeObserver.observe(this.$refs.plan); }, beforeUnmount() { - clearInterval(this.resizeTimer); + this.resizeObserver.disconnect(); this.stopFollowingTimeContext(); if (this.unlisten) { this.unlisten(); diff --git a/src/plugins/remoteClock/RemoteClock.js b/src/plugins/remoteClock/RemoteClock.js index b97ae8d027..b7c6407855 100644 --- a/src/plugins/remoteClock/RemoteClock.js +++ b/src/plugins/remoteClock/RemoteClock.js @@ -152,15 +152,18 @@ export default class RemoteClock extends DefaultClock { */ #waitForReady() { const waitForInitialTick = (resolve) => { - if (this.lastTick > 0) { - const offsets = this.openmct.time.getClockOffsets(); - resolve({ - start: this.lastTick + offsets.start, - end: this.lastTick + offsets.end - }); - } else { - setTimeout(() => waitForInitialTick(resolve), 100); - } + const tickListener = () => { + if (this.lastTick > 0) { + const offsets = this.openmct.time.getClockOffsets(); + this.openmct.time.off('tick', tickListener); // Unregister the tick listener + resolve({ + start: this.lastTick + offsets.start, + end: this.lastTick + offsets.end + }); + } + }; + + this.openmct.time.on('tick', tickListener); }; return new Promise(waitForInitialTick); diff --git a/src/plugins/remoteClock/requestInterceptor.js b/src/plugins/remoteClock/requestInterceptor.js index 7fc5b176d6..f4a075970f 100644 --- a/src/plugins/remoteClock/requestInterceptor.js +++ b/src/plugins/remoteClock/requestInterceptor.js @@ -20,16 +20,43 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ +/** + * Intercepts requests to ensure the remote clock is ready. + * + * @param {import('../../openmct').OpenMCT} openmct - The OpenMCT instance. + * @param {import('../../openmct').Identifier} _remoteClockIdentifier - The identifier for the remote clock. + * @param {Function} waitForBounds - A function that returns a promise resolving to the initial bounds. + * @returns {Object} The request interceptor. + */ function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) { let remoteClockLoaded = false; return { - appliesTo: () => { + /** + * Determines if the interceptor applies to the given request. + * + * @param {Object} _ - Unused parameter. + * @param {import('../../api/telemetry/TelemetryAPI').TelemetryRequestOptions} request - The request object. + * @returns {boolean} True if the interceptor applies, false otherwise. + */ + appliesTo: (_, request) => { // Get the activeClock from the Global Time Context + /** @type {import("../../api/time/TimeContext").default} */ const { activeClock } = openmct.time; + // this type of request does not rely on clock having bounds + if (request.strategy === 'latest' && request.timeContext.isRealTime()) { + return false; + } + return activeClock?.key === 'remote-clock' && !remoteClockLoaded; }, + /** + * Invokes the interceptor to modify the request. + * + * @param {Object} request - The request object. + * @returns {Promise} The modified request object. + */ invoke: async (request) => { const timeContext = request?.timeContext ?? openmct.time; diff --git a/src/plugins/timeConductor/ConductorInputsRealtime.vue b/src/plugins/timeConductor/ConductorInputsRealtime.vue index c3c8f2db77..ec98b823f5 100644 --- a/src/plugins/timeConductor/ConductorInputsRealtime.vue +++ b/src/plugins/timeConductor/ConductorInputsRealtime.vue @@ -150,7 +150,7 @@ export default { mounted() { this.handleNewBounds = _.throttle(this.handleNewBounds, 300, { leading: true, - trailing: false + trailing: true }); this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem())); this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem); @@ -181,6 +181,8 @@ export default { } }, stopFollowingTime() { + this.handleNewBounds.cancel(); + if (this.timeContext) { this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds); this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets); diff --git a/src/ui/components/progress-bar.scss b/src/ui/components/progress-bar.scss index 4fd1052072..c1416d3890 100644 --- a/src/ui/components/progress-bar.scss +++ b/src/ui/components/progress-bar.scss @@ -1,16 +1,13 @@ /******************************************************** PROGRESS BAR */ @keyframes progressIndeterminate { 0% { - left: 0; - width: 0; + transform:scaleX(0); } - 70% { - left: 0; - width: 100%; + 90% { + transform:scaleX(1); opacity: 1; } 100% { - left: 100%; opacity: 0; } } @@ -24,11 +21,10 @@ &__bar { background: $colorProgressBar; - height: 100%; - min-height: $progressBarMinH; + transform-origin: left; &.--indeterminate { - position: absolute; + @include abs(); animation: progressIndeterminate 1.5s ease-in infinite; } }