Throttle chart stale retry badges

This commit is contained in:
2569718930@qq.com
2026-06-08 13:59:38 +08:00
parent 956cb8a8e3
commit 505b75d6ce
4 changed files with 23 additions and 6 deletions
@@ -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");