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 <jesse.d.mazzella@nasa.gov>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
David Tsay 2024-10-08 13:10:35 -07:00 committed by GitHub
parent 83e4a124e2
commit 4415fe7952
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 85 additions and 56 deletions

View File

@ -231,26 +231,20 @@ export default class TelemetryAPI {
* @returns {TelemetryRequestOptions} the options, with defaults filled in * @returns {TelemetryRequestOptions} the options, with defaults filled in
*/ */
standardizeRequestOptions(options = {}) { standardizeRequestOptions(options = {}) {
if (!Object.hasOwn(options, 'start')) { if (!Object.hasOwn(options, 'timeContext')) {
const bounds = options.timeContext?.getBounds(); options.timeContext = this.openmct.time;
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, 'domain')) { 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; return options;

View File

@ -269,36 +269,40 @@ describe('Telemetry API', () => {
await telemetryAPI.request(domainObject); await telemetryAPI.request(domainObject);
const { signal } = new AbortController(); const { signal } = new AbortController();
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
signal, signal,
start: 0, start: 0,
end: 1, end: 1,
domain: 'system' domain: 'system',
timeContext: openmct.time
}); });
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
signal, signal,
start: 0, start: 0,
end: 1, end: 1,
domain: 'system' domain: 'system',
timeContext: openmct.time
}); });
telemetryProvider.supportsRequest.calls.reset(); telemetryProvider.supportsRequest.calls.reset();
telemetryProvider.request.calls.reset(); telemetryProvider.request.calls.reset();
await telemetryAPI.request(domainObject, {}); await telemetryAPI.request(domainObject, {});
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
signal, signal,
start: 0, start: 0,
end: 1, end: 1,
domain: 'system' domain: 'system',
timeContext: openmct.time
}); });
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
signal, signal,
start: 0, start: 0,
end: 1, end: 1,
domain: 'system' domain: 'system',
timeContext: openmct.time
}); });
}); });
@ -313,18 +317,20 @@ describe('Telemetry API', () => {
domain: 'someDomain' domain: 'someDomain'
}); });
const { signal } = new AbortController(); const { signal } = new AbortController();
expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.supportsRequest).toHaveBeenCalledWith(domainObject, {
start: 20, start: 20,
end: 30, end: 30,
domain: 'someDomain', domain: 'someDomain',
signal signal,
timeContext: openmct.time
}); });
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), { expect(telemetryProvider.request).toHaveBeenCalledWith(domainObject, {
start: 20, start: 20,
end: 30, end: 30,
domain: 'someDomain', domain: 'someDomain',
signal signal,
timeContext: openmct.time
}); });
}); });
describe('telemetry batching support', () => { describe('telemetry batching support', () => {

View File

@ -62,9 +62,6 @@ export default class TelemetryCollection extends EventEmitter {
this.futureBuffer = []; this.futureBuffer = [];
this.parseTime = undefined; this.parseTime = undefined;
this.metadata = this.openmct.telemetry.getMetadata(domainObject); this.metadata = this.openmct.telemetry.getMetadata(domainObject);
if (!Object.hasOwn(options, 'timeContext')) {
options.timeContext = this.openmct.time;
}
this.options = options; this.options = options;
this.unsubscribe = undefined; this.unsubscribe = undefined;
this.pageState = undefined; this.pageState = undefined;
@ -84,6 +81,9 @@ export default class TelemetryCollection extends EventEmitter {
this._error(LOADED_ERROR); this._error(LOADED_ERROR);
} }
if (!Object.hasOwn(this.options, 'timeContext')) {
this.options.timeContext = this.openmct.time;
}
this._setTimeSystem(this.options.timeContext.getTimeSystem()); this._setTimeSystem(this.options.timeContext.getTimeSystem());
this.lastBounds = this.options.timeContext.getBounds(); this.lastBounds = this.options.timeContext.getBounds();
this._watchBounds(); this._watchBounds();
@ -127,7 +127,7 @@ export default class TelemetryCollection extends EventEmitter {
* @private * @private
*/ */
async _requestHistoricalTelemetry() { async _requestHistoricalTelemetry() {
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options }); const options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
const historicalProvider = this.openmct.telemetry.findRequestProvider( const historicalProvider = this.openmct.telemetry.findRequestProvider(
this.domainObject, this.domainObject,
options options

View File

@ -69,7 +69,6 @@ const INNER_TEXT_PADDING = 15;
const TEXT_LEFT_PADDING = 5; const TEXT_LEFT_PADDING = 5;
const ROW_PADDING = 5; const ROW_PADDING = 5;
const SWIMLANE_PADDING = 3; const SWIMLANE_PADDING = 3;
const RESIZE_POLL_INTERVAL = 200;
const ROW_HEIGHT = 22; const ROW_HEIGHT = 22;
const MAX_TEXT_WIDTH = 300; const MAX_TEXT_WIDTH = 300;
const MIN_ACTIVITY_WIDTH = 2; const MIN_ACTIVITY_WIDTH = 2;
@ -143,13 +142,15 @@ export default {
this.canvasContext = canvas.getContext('2d'); this.canvasContext = canvas.getContext('2d');
this.setDimensions(); this.setDimensions();
this.setTimeContext(); this.setTimeContext();
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
this.handleConfigurationChange(this.configuration); this.handleConfigurationChange(this.configuration);
this.planViewConfiguration.on('change', this.handleConfigurationChange); this.planViewConfiguration.on('change', this.handleConfigurationChange);
this.loadComposition(); this.loadComposition();
this.resizeObserver = new ResizeObserver(this.resize);
this.resizeObserver.observe(this.$refs.plan);
}, },
beforeUnmount() { beforeUnmount() {
clearInterval(this.resizeTimer); this.resizeObserver.disconnect();
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
if (this.unlisten) { if (this.unlisten) {
this.unlisten(); this.unlisten();

View File

@ -152,15 +152,18 @@ export default class RemoteClock extends DefaultClock {
*/ */
#waitForReady() { #waitForReady() {
const waitForInitialTick = (resolve) => { const waitForInitialTick = (resolve) => {
if (this.lastTick > 0) { const tickListener = () => {
const offsets = this.openmct.time.getClockOffsets(); if (this.lastTick > 0) {
resolve({ const offsets = this.openmct.time.getClockOffsets();
start: this.lastTick + offsets.start, this.openmct.time.off('tick', tickListener); // Unregister the tick listener
end: this.lastTick + offsets.end resolve({
}); start: this.lastTick + offsets.start,
} else { end: this.lastTick + offsets.end
setTimeout(() => waitForInitialTick(resolve), 100); });
} }
};
this.openmct.time.on('tick', tickListener);
}; };
return new Promise(waitForInitialTick); return new Promise(waitForInitialTick);

View File

@ -20,16 +20,43 @@
* at runtime from the About dialog for additional information. * 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) { function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
let remoteClockLoaded = false; let remoteClockLoaded = false;
return { 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 // Get the activeClock from the Global Time Context
/** @type {import("../../api/time/TimeContext").default} */
const { activeClock } = openmct.time; 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; return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
}, },
/**
* Invokes the interceptor to modify the request.
*
* @param {Object} request - The request object.
* @returns {Promise<Object>} The modified request object.
*/
invoke: async (request) => { invoke: async (request) => {
const timeContext = request?.timeContext ?? openmct.time; const timeContext = request?.timeContext ?? openmct.time;

View File

@ -150,7 +150,7 @@ export default {
mounted() { mounted() {
this.handleNewBounds = _.throttle(this.handleNewBounds, 300, { this.handleNewBounds = _.throttle(this.handleNewBounds, 300, {
leading: true, leading: true,
trailing: false trailing: true
}); });
this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem())); this.setTimeSystem(this.copy(this.openmct.time.getTimeSystem()));
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem); this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setTimeSystem);
@ -181,6 +181,8 @@ export default {
} }
}, },
stopFollowingTime() { stopFollowingTime() {
this.handleNewBounds.cancel();
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds); this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.handleNewBounds);
this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets); this.timeContext.off(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.setViewFromOffsets);

View File

@ -1,16 +1,13 @@
/******************************************************** PROGRESS BAR */ /******************************************************** PROGRESS BAR */
@keyframes progressIndeterminate { @keyframes progressIndeterminate {
0% { 0% {
left: 0; transform:scaleX(0);
width: 0;
} }
70% { 90% {
left: 0; transform:scaleX(1);
width: 100%;
opacity: 1; opacity: 1;
} }
100% { 100% {
left: 100%;
opacity: 0; opacity: 0;
} }
} }
@ -24,11 +21,10 @@
&__bar { &__bar {
background: $colorProgressBar; background: $colorProgressBar;
height: 100%; transform-origin: left;
min-height: $progressBarMinH;
&.--indeterminate { &.--indeterminate {
position: absolute; @include abs();
animation: progressIndeterminate 1.5s ease-in infinite; animation: progressIndeterminate 1.5s ease-in infinite;
} }
} }