Throttle chart stale retry badges
This commit is contained in:
@@ -11,6 +11,7 @@ import { TemperatureChartCanvas } from "@/components/dashboard/scan-terminal/Tem
|
|||||||
import { TemperatureRunwayDetails } from "@/components/dashboard/scan-terminal/TemperatureRunwayDetails";
|
import { TemperatureRunwayDetails } from "@/components/dashboard/scan-terminal/TemperatureRunwayDetails";
|
||||||
import { TemperatureStatsBars } from "@/components/dashboard/scan-terminal/TemperatureStatsBars";
|
import { TemperatureStatsBars } from "@/components/dashboard/scan-terminal/TemperatureStatsBars";
|
||||||
import { rowName } from "@/components/dashboard/scan-terminal/utils";
|
import { rowName } from "@/components/dashboard/scan-terminal/utils";
|
||||||
|
import { DASHBOARD_REFRESH_POLICY_MS } from "@/lib/refresh-policy";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HOURLY_CACHE_TTL_MS,
|
HOURLY_CACHE_TTL_MS,
|
||||||
@@ -58,6 +59,7 @@ const PEAK_GLOW_BADGE_CLASS = {
|
|||||||
|
|
||||||
const PROBABILITY_REFRESH_AFTER_PATCH_MS = 60_000;
|
const PROBABILITY_REFRESH_AFTER_PATCH_MS = 60_000;
|
||||||
const FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS = 90_000;
|
const FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS = 90_000;
|
||||||
|
const NO_PATCH_FULL_DETAIL_FALLBACK_MS = DASHBOARD_REFRESH_POLICY_MS.metar;
|
||||||
const DETAIL_LOAD_BATCH_DELAY_MS = 0;
|
const DETAIL_LOAD_BATCH_DELAY_MS = 0;
|
||||||
const INITIAL_DETAIL_LOAD_SLOTS = 3;
|
const INITIAL_DETAIL_LOAD_SLOTS = 3;
|
||||||
const DEFERRED_DETAIL_LOAD_DELAY_MS = 1_200;
|
const DEFERRED_DETAIL_LOAD_DELAY_MS = 1_200;
|
||||||
@@ -663,7 +665,7 @@ export function LiveTemperatureThresholdChart({
|
|||||||
};
|
};
|
||||||
}, [resyncVersion, city, targetResolution]);
|
}, [resyncVersion, city, targetResolution]);
|
||||||
|
|
||||||
// ── SSE fallback: only full-fetch if a visible chart has seen no patch for 2 minutes ──
|
// ── SSE fallback: only full-fetch if a visible chart has seen no patch for one METAR cadence ──
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldPollLiveChart({ city, compact, isActive, isMaximized })) return;
|
if (!shouldPollLiveChart({ city, compact, isActive, isMaximized })) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -687,7 +689,7 @@ export function LiveTemperatureThresholdChart({
|
|||||||
|
|
||||||
const checkFallback = () => {
|
const checkFallback = () => {
|
||||||
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
||||||
if (Date.now() - lastPatchAtRef.current < 2 * 60_000) return;
|
if (Date.now() - lastPatchAtRef.current < NO_PATCH_FULL_DETAIL_FALLBACK_MS) return;
|
||||||
|
|
||||||
refreshFullDetail();
|
refreshFullDetail();
|
||||||
};
|
};
|
||||||
@@ -966,6 +968,7 @@ export function LiveTemperatureThresholdChart({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const subtitle = row ? (isEn ? "Live & Forecast" : "实测与预测") : "";
|
const subtitle = row ? (isEn ? "Live & Forecast" : "实测与预测") : "";
|
||||||
|
const showDetailErrorBadge = !compact || isActive || isMaximized;
|
||||||
|
|
||||||
const handleZoomReset = useCallback(() => {
|
const handleZoomReset = useCallback(() => {
|
||||||
setZoomRange(null);
|
setZoomRange(null);
|
||||||
@@ -1259,6 +1262,7 @@ export function LiveTemperatureThresholdChart({
|
|||||||
isHourlyLoading={isHourlyLoading}
|
isHourlyLoading={isHourlyLoading}
|
||||||
detailError={detailError}
|
detailError={detailError}
|
||||||
showingStaleDetail={showingStaleDetail}
|
showingStaleDetail={showingStaleDetail}
|
||||||
|
showDetailErrorBadge={showDetailErrorBadge}
|
||||||
refAreaLeft={refAreaLeft}
|
refAreaLeft={refAreaLeft}
|
||||||
refAreaRight={refAreaRight}
|
refAreaRight={refAreaRight}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ function TemperatureChartCanvasComponent({
|
|||||||
isHourlyLoading,
|
isHourlyLoading,
|
||||||
detailError,
|
detailError,
|
||||||
showingStaleDetail,
|
showingStaleDetail,
|
||||||
|
showDetailErrorBadge = true,
|
||||||
refAreaLeft,
|
refAreaLeft,
|
||||||
refAreaRight,
|
refAreaRight,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
@@ -139,6 +140,7 @@ function TemperatureChartCanvasComponent({
|
|||||||
isHourlyLoading: boolean;
|
isHourlyLoading: boolean;
|
||||||
detailError?: string | null;
|
detailError?: string | null;
|
||||||
showingStaleDetail?: boolean;
|
showingStaleDetail?: boolean;
|
||||||
|
showDetailErrorBadge?: boolean;
|
||||||
refAreaLeft: number | null;
|
refAreaLeft: number | null;
|
||||||
refAreaRight: number | null;
|
refAreaRight: number | null;
|
||||||
onMouseDown: (event: any) => void;
|
onMouseDown: (event: any) => void;
|
||||||
@@ -223,7 +225,7 @@ function TemperatureChartCanvasComponent({
|
|||||||
const shouldShowBackgroundRefresh = isHourlyLoading && hasDrawableChartContent;
|
const shouldShowBackgroundRefresh = isHourlyLoading && hasDrawableChartContent;
|
||||||
const shouldShowUnavailableState = Boolean(row?.city) && Boolean(detailError) && !isHourlyLoading && !hasDrawableChartContent;
|
const shouldShowUnavailableState = Boolean(row?.city) && Boolean(detailError) && !isHourlyLoading && !hasDrawableChartContent;
|
||||||
const shouldShowBackgroundError =
|
const shouldShowBackgroundError =
|
||||||
Boolean(row?.city) && Boolean(detailError) && !isHourlyLoading && hasDrawableChartContent;
|
showDetailErrorBadge && Boolean(row?.city) && Boolean(detailError) && !isHourlyLoading && hasDrawableChartContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("relative flex flex-1 flex-col p-2", compact ? "min-h-[120px]" : "min-h-[240px]")}>
|
<div className={clsx("relative flex flex-1 flex-col p-2", compact ? "min-h-[120px]" : "min-h-[240px]")}>
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ export async function runTests() {
|
|||||||
assert(
|
assert(
|
||||||
chartSource.includes("useLatestPatch") &&
|
chartSource.includes("useLatestPatch") &&
|
||||||
chartSource.includes("latestPatch") &&
|
chartSource.includes("latestPatch") &&
|
||||||
chartSource.includes("2 * 60_000"),
|
chartSource.includes("DASHBOARD_REFRESH_POLICY_MS.metar") &&
|
||||||
"selected city chart should consume SSE patches and use a 2-minute no-patch fallback",
|
!chartSource.includes("2 * 60_000"),
|
||||||
|
"selected city chart should consume SSE patches and use a METAR-cadence no-patch fallback instead of a 2-minute forced refresh",
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
chartSource.includes("preloadTemperatureChartCanvas"),
|
chartSource.includes("preloadTemperatureChartCanvas"),
|
||||||
@@ -184,6 +185,12 @@ export async function runTests() {
|
|||||||
chartCanvasSourceIncludes(chartSource, "handleRetryDetail"),
|
chartCanvasSourceIncludes(chartSource, "handleRetryDetail"),
|
||||||
"city detail charts should show stale cache first and expose a retryable unavailable state",
|
"city detail charts should show stale cache first and expose a retryable unavailable state",
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
chartSource.includes("const showDetailErrorBadge = !compact || isActive || isMaximized") &&
|
||||||
|
chartSource.includes("showDetailErrorBadge={showDetailErrorBadge}") &&
|
||||||
|
chartCanvasSourceIncludes(chartSource, "showDetailErrorBadge"),
|
||||||
|
"compact grid charts should only show the stale-cache retry badge on the active slot or expanded chart",
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
__shouldFetchCityDetailForChartForTest({ city: "paris", documentHidden: false, isChartVisible: true }) === true,
|
__shouldFetchCityDetailForChartForTest({ city: "paris", documentHidden: false, isChartVisible: true }) === true,
|
||||||
"visible chart cards should fetch city detail",
|
"visible chart cards should fetch city detail",
|
||||||
|
|||||||
@@ -148,7 +148,11 @@ export function runTests() {
|
|||||||
assert(chart.includes("latestPatch"), "temperature chart must react to incoming SSE patches");
|
assert(chart.includes("latestPatch"), "temperature chart must react to incoming SSE patches");
|
||||||
assert(chart.includes("useSseResyncVersion"), "temperature chart must resync full detail when SSE replay is incomplete");
|
assert(chart.includes("useSseResyncVersion"), "temperature chart must resync full detail when SSE replay is incomplete");
|
||||||
assert(chartLogic.includes("runway_points"), "temperature chart must merge v1 runway_points into runway history");
|
assert(chartLogic.includes("runway_points"), "temperature chart must merge v1 runway_points into runway history");
|
||||||
assert(chart.includes("2 * 60_000"), "temperature chart must wait two minutes without patches before full-fetch fallback");
|
assert(
|
||||||
|
chart.includes("DASHBOARD_REFRESH_POLICY_MS.metar") &&
|
||||||
|
!chart.includes("2 * 60_000"),
|
||||||
|
"temperature chart must wait one METAR cadence without patches before full-fetch fallback",
|
||||||
|
);
|
||||||
assert(chart.includes("TemperatureChartCanvas"), "temperature chart shell must compose the extracted chart canvas");
|
assert(chart.includes("TemperatureChartCanvas"), "temperature chart shell must compose the extracted chart canvas");
|
||||||
assert(chart.includes("TemperatureStatsBars"), "temperature chart shell must compose the extracted stat bars");
|
assert(chart.includes("TemperatureStatsBars"), "temperature chart shell must compose the extracted stat bars");
|
||||||
assert(chart.includes("TemperatureRunwayDetails"), "temperature chart shell must compose the extracted runway panel");
|
assert(chart.includes("TemperatureRunwayDetails"), "temperature chart shell must compose the extracted runway panel");
|
||||||
|
|||||||
Reference in New Issue
Block a user