Compare commits

...

2 Commits

Author SHA1 Message Date
Sung-Hyun Kang
ddd757b3a0 run prettier 2026-02-24 15:01:02 -06:00
Sung-Hyun Kang
1d0914e1f9 Fixing metrics 2026-02-24 10:58:20 -06:00
2 changed files with 30 additions and 8 deletions

View File

@@ -105,6 +105,23 @@ class ScenarioMonitor {
});
ctx.timeoutId = window.setTimeout(() => {
const missingPhases = ctx.config.requiredPhases.filter((p) => !ctx.completed.has(p));
this.devLog(
`timeout: ${scenario} | missing=[${missingPhases.join(", ")}] | completed=[${Array.from(ctx.completed).join(
", ",
)}] | documentHidden=${document.hidden} | hasExpectedFailure=${ctx.hasExpectedFailure}`,
);
traceMark(Action.MetricsScenario, {
event: "scenario_timeout",
scenario,
missingPhases: missingPhases.join(","),
completedPhases: Array.from(ctx.completed).join(","),
documentHidden: document.hidden,
hasExpectedFailure: ctx.hasExpectedFailure,
});
// If an expected failure occurred (auth, firewall, etc.), emit healthy instead of unhealthy
const healthy = ctx.hasExpectedFailure;
this.emit(ctx, healthy, true);
@@ -288,6 +305,7 @@ class ScenarioMonitor {
scenario: ctx.scenario,
healthy,
timedOut,
documentHidden: document.hidden,
platform,
api,
durationMs: finalSnapshot.durationMs,

View File

@@ -1,24 +1,26 @@
import React from "react";
import MetricScenario from "./MetricEvents";
import { useMetricScenario } from "./MetricScenarioProvider";
import { scenarioMonitor } from "./ScenarioMonitor";
import { ApplicationMetricPhase, CommonMetricPhase } from "./ScenarioConfig";
/**
* Hook to automatically complete the Interactive phase when the component becomes interactive.
* Uses requestAnimationFrame to complete after the browser has painted.
*
* Calls scenarioMonitor directly (not via React context) so that the effect dependencies
* are only [scenario, enabled] — both stable primitives. This prevents re-renders from
* cancelling the pending rAF due to an unstable context function reference.
*/
export function useInteractive(scenario: MetricScenario, enabled = true) {
const { completePhase } = useMetricScenario();
React.useEffect(() => {
if (!enabled) {
return undefined;
}
const id = requestAnimationFrame(() => {
completePhase(scenario, CommonMetricPhase.Interactive);
scenarioMonitor.completePhase(scenario, CommonMetricPhase.Interactive);
});
return () => cancelAnimationFrame(id);
}, [scenario, completePhase, enabled]);
}, [scenario, enabled]);
}
/**
@@ -26,18 +28,20 @@ export function useInteractive(scenario: MetricScenario, enabled = true) {
* Tracks tree rendering and completes Interactive phase.
* Only completes DatabaseTreeRendered if the database fetch was successful.
* Note: Scenario must be started before databases are fetched (in refreshExplorer).
*
* Calls scenarioMonitor directly (not via React context) for the same stability reason
* as useInteractive — avoids effect re-runs from unstable context function references.
*/
export function useDatabaseLoadScenario(databaseTreeNodes: unknown[], fetchSucceeded: boolean) {
const { completePhase } = useMetricScenario();
const hasCompletedTreeRenderRef = React.useRef(false);
// Track DatabaseTreeRendered phase (only if fetch succeeded)
React.useEffect(() => {
if (!hasCompletedTreeRenderRef.current && fetchSucceeded) {
hasCompletedTreeRenderRef.current = true;
completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
}
}, [databaseTreeNodes, fetchSucceeded, completePhase]);
}, [databaseTreeNodes, fetchSucceeded]);
// Track Interactive phase
useInteractive(MetricScenario.DatabaseLoad);