mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-09-19 17:12:47 +01:00
Fix Data Explorer health scenarios reporting false unhealthy loads
ScenarioMonitor.completePhase() silently no-oped when the phase had not been started yet, and the production call ordering always hits that case: - DatabaseTreeRendered is started in Explorer.tsx after the last databaseTreeNodes change, but the effect that completes it only re-fires on such a change and databaseTreeNodes is memoised, so the phase never closed. - Interactive is completed by a one-shot effect whose deps are stable, so it is lost entirely when ResourceTree mounts before refreshExplorer starts the scenario. Over three days DatabaseTreeRendered was missing in 1624 of 1627 DatabaseLoad timeouts (99.8%) and Interactive in 588, driving DataExplorerHealthV2 to report loads as unhealthy that had in fact completed. completePhase now self-starts a required phase that is completed before startPhase, and completions reported before the scenario exists are buffered and replayed on start(). A timeout while the tab is backgrounded no longer counts against health either: browsers throttle timers and suspend rAF there, so phase completion is unreliable and the elapsed time is not the user's experience. documentHidden is still reported so those can be sliced out in telemetry. Separately, configurePortal() resolved only from the iframe message handler with no timeout. When the portal never posts the init message the promise stayed pending forever, leaving the user on LoadingExplorer with no diagnostic while ApplicationLoad timed out mute. It now rejects after 30s with a ConfigurePortal failure trace, and the caller handles the rejection instead of leaving it unhandled. Related: IcM 865096261
This commit is contained in:
@@ -108,7 +108,14 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||
scenarioMonitor.completePhase(MetricScenario.ApplicationLoad, ApplicationMetricPhase.ExplorerInitialized);
|
||||
}
|
||||
};
|
||||
effect();
|
||||
effect().catch((error) => {
|
||||
// configurePortal now rejects instead of hanging forever, so this is reachable.
|
||||
// Without a handler it would surface only as an unhandled rejection.
|
||||
Logger.logError(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
"useKnockoutExplorer/configure",
|
||||
);
|
||||
});
|
||||
}, [platform]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -705,6 +712,13 @@ export async function fetchAndUpdateKeys(subscriptionId: string, resourceGroup:
|
||||
}
|
||||
}
|
||||
|
||||
// The portal configures Data Explorer with a single iframe message. If a dependency on the
|
||||
// portal side never resolves, that message is never posted — and without this timeout the
|
||||
// promise below stays pending forever, leaving the user on <LoadingExplorer /> with no
|
||||
// diagnostic while the ApplicationLoad health scenario times out mute (IcM 865096261).
|
||||
// Deliberately well above the 10s scenario budget so only genuinely stuck handshakes fail.
|
||||
const PORTAL_INIT_MESSAGE_TIMEOUT_MS = 30000;
|
||||
|
||||
async function configurePortal(): Promise<Explorer> {
|
||||
const configureStartKey = traceStart(Action.ConfigurePortal, {
|
||||
dataExplorerArea: "ResourceTree",
|
||||
@@ -714,7 +728,9 @@ async function configurePortal(): Promise<Explorer> {
|
||||
});
|
||||
|
||||
let explorer: Explorer;
|
||||
return new Promise((resolve) => {
|
||||
let initMessageTimeoutId: number;
|
||||
|
||||
const explorerReady = new Promise<Explorer>((resolve) => {
|
||||
// In development mode, try to load the iframe message from session storage.
|
||||
// This allows webpack hot reload to function properly in the portal
|
||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||
@@ -849,6 +865,22 @@ async function configurePortal(): Promise<Explorer> {
|
||||
|
||||
sendReadyMessage();
|
||||
});
|
||||
|
||||
const initMessageTimeout = new Promise<never>((_resolve, reject) => {
|
||||
initMessageTimeoutId = window.setTimeout(() => {
|
||||
const error = new Error(
|
||||
`Portal did not send the Data Explorer init message within ${PORTAL_INIT_MESSAGE_TIMEOUT_MS}ms`,
|
||||
);
|
||||
traceFailure(Action.ConfigurePortal, { error: error.message }, configureStartKey);
|
||||
reject(error);
|
||||
}, PORTAL_INIT_MESSAGE_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
try {
|
||||
return await Promise.race([explorerReady, initMessageTimeout]);
|
||||
} finally {
|
||||
window.clearTimeout(initMessageTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
||||
|
||||
Reference in New Issue
Block a user