Refresh terminal charts on activation
This commit is contained in:
+2
-2
@@ -106,8 +106,8 @@ services:
|
||||
POLYWEATHER_REDIS_STREAM_MAXLEN: ${POLYWEATHER_REDIS_STREAM_MAXLEN:-100000}
|
||||
POLYWEATHER_SCAN_TERMINAL_REDIS_CACHE_ENABLED: ${POLYWEATHER_SCAN_TERMINAL_REDIS_CACHE_ENABLED:-true}
|
||||
POLYWEATHER_COLLECTOR_PATCH_ENDPOINT: ''
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY:-2}
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY:-2}
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY:-3}
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY:-3}
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_QUEUE_WAIT_MS: ${POLYWEATHER_CITY_DETAIL_BATCH_QUEUE_WAIT_MS:-3000}
|
||||
POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS: ${POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS:-8000}
|
||||
POLYWEATHER_OBSERVATION_COLLECTOR_AMOS_SEC: ${POLYWEATHER_OBSERVATION_COLLECTOR_AMOS_SEC:-60}
|
||||
|
||||
@@ -617,6 +617,7 @@ function PolyWeatherTerminal({
|
||||
generatedText,
|
||||
isEn,
|
||||
locale,
|
||||
onTerminalActivated,
|
||||
onRefresh,
|
||||
refreshing,
|
||||
rows,
|
||||
@@ -636,10 +637,12 @@ function PolyWeatherTerminal({
|
||||
setSelectedRegionKey,
|
||||
visibleRegions,
|
||||
toggleRegion,
|
||||
terminalActivationRefreshKey,
|
||||
}: {
|
||||
generatedText: string;
|
||||
isEn: boolean;
|
||||
locale: "zh-CN" | "en-US";
|
||||
onTerminalActivated: () => void;
|
||||
onRefresh: () => void;
|
||||
refreshing: boolean;
|
||||
rows: ScanOpportunityRow[];
|
||||
@@ -659,6 +662,7 @@ function PolyWeatherTerminal({
|
||||
setSelectedRegionKey: (key: string) => void;
|
||||
visibleRegions: Set<string>;
|
||||
toggleRegion: (key: string) => void;
|
||||
terminalActivationRefreshKey: number;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -682,6 +686,7 @@ function PolyWeatherTerminal({
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [searchInputRef, setSearchQuery]);
|
||||
const [activeNavKey, setActiveNavKey] = useState<string>("thresholds");
|
||||
const previousActiveNavKeyRef = useRef(activeNavKey);
|
||||
const [onlineCount, setOnlineCount] = useState<number | null>(null);
|
||||
const [feedbackDraft, setFeedbackDraft] = useState<FeedbackDraft | null>(null);
|
||||
const [feedbackRefreshKey, setFeedbackRefreshKey] = useState(0);
|
||||
@@ -705,6 +710,13 @@ function PolyWeatherTerminal({
|
||||
void preloadTemperatureChartCanvas();
|
||||
}, [activeNavKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const previousActiveNavKey = previousActiveNavKeyRef.current;
|
||||
previousActiveNavKeyRef.current = activeNavKey;
|
||||
if (activeNavKey !== "thresholds" || previousActiveNavKey === "thresholds") return;
|
||||
onTerminalActivated();
|
||||
}, [activeNavKey, onTerminalActivated]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOnline = () => {
|
||||
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
||||
@@ -1078,6 +1090,7 @@ function PolyWeatherTerminal({
|
||||
row={mobileChartRow}
|
||||
allRows={filteredRegionRows}
|
||||
compact={false}
|
||||
activationRefreshKey={terminalActivationRefreshKey}
|
||||
disableClose={true}
|
||||
onReportIssue={openChartFeedback}
|
||||
/>
|
||||
@@ -1133,6 +1146,7 @@ function PolyWeatherTerminal({
|
||||
row={filteredRegionRows.find((r) => String(r.city || "").toLowerCase() === visibleSlots[maximizedSlotIndex]) || null}
|
||||
allRows={filteredRegionRows}
|
||||
compact={false}
|
||||
activationRefreshKey={terminalActivationRefreshKey}
|
||||
onSearchClick={() => setActiveSearchSlotIndex(maximizedSlotIndex)}
|
||||
onMaximize={() => setMaximizedSlotIndex(null)}
|
||||
onClose={() => {
|
||||
@@ -1217,6 +1231,7 @@ function PolyWeatherTerminal({
|
||||
row={rowForSlot}
|
||||
allRows={filteredRegionRows}
|
||||
compact={true}
|
||||
activationRefreshKey={terminalActivationRefreshKey}
|
||||
isActive={isSlotActive}
|
||||
slotIndex={slotIndex}
|
||||
onSearchClick={() => setActiveSearchSlotIndex(slotIndex)}
|
||||
@@ -1639,11 +1654,16 @@ function ScanTerminalScreen() {
|
||||
setUseLocalTimezoneDefault(false);
|
||||
setSelectedRegionKey(key);
|
||||
}, []);
|
||||
const [terminalActivationRefreshKey, setTerminalActivationRefreshKey] = useState(0);
|
||||
const handleTerminalActivated = useCallback(() => {
|
||||
setTerminalActivationRefreshKey((value) => value + 1);
|
||||
}, []);
|
||||
|
||||
const { refreshScanTerminalManually, scanLoading, terminalData } =
|
||||
useScanTerminalQuery({
|
||||
isPro,
|
||||
proAccessLoading: !hydrated || (proAccess.loading && !canUseLocalFullAccess),
|
||||
terminalActivationRefreshKey,
|
||||
timezoneOffsetSeconds: useLocalTimezoneDefault ? localTimezoneOffsetSeconds : null,
|
||||
tradingRegion: selectedRegionKey,
|
||||
});
|
||||
@@ -1800,6 +1820,7 @@ function ScanTerminalScreen() {
|
||||
generatedText={generatedText || ""}
|
||||
isEn={isEn}
|
||||
locale={locale}
|
||||
onTerminalActivated={handleTerminalActivated}
|
||||
onRefresh={handleRefresh}
|
||||
refreshing={scanLoading}
|
||||
rows={filteredRows}
|
||||
@@ -1821,6 +1842,7 @@ function ScanTerminalScreen() {
|
||||
setSelectedRegionKey={selectRegionManually}
|
||||
visibleRegions={visibleRegions}
|
||||
toggleRegion={toggleRegion}
|
||||
terminalActivationRefreshKey={terminalActivationRefreshKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -542,6 +542,7 @@ export function LiveTemperatureThresholdChart({
|
||||
isMaximized = false,
|
||||
disableClose = false,
|
||||
isActive = !compact,
|
||||
activationRefreshKey = 0,
|
||||
slotIndex = 0,
|
||||
}: {
|
||||
isEn: boolean;
|
||||
@@ -555,6 +556,7 @@ export function LiveTemperatureThresholdChart({
|
||||
isMaximized?: boolean;
|
||||
disableClose?: boolean;
|
||||
isActive?: boolean;
|
||||
activationRefreshKey?: number;
|
||||
slotIndex?: number;
|
||||
}) {
|
||||
const [hourly, setHourly] = useState<HourlyForecast>(null);
|
||||
@@ -986,6 +988,56 @@ export function LiveTemperatureThresholdChart({
|
||||
};
|
||||
}, [city, compact, isActive, isMaximized, targetResolution, markDetailDegraded, markDetailRequest, applySuccessfulHourlyDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activationRefreshKey) return;
|
||||
if (!shouldPollLiveChart({ city, compact, isActive, isMaximized })) return;
|
||||
if (typeof document !== "undefined" && document.visibilityState !== "visible") return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const refreshActivatedCachedDetail = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastForegroundRefreshAtRef.current < 10_000) return;
|
||||
|
||||
lastForegroundRefreshAtRef.current = now;
|
||||
lastPatchAtRef.current = now;
|
||||
markDetailRequest("network");
|
||||
|
||||
fetchHourlyForecastForCity(city, { bypassLocalCache: true, resolution: targetResolution })
|
||||
.then((data) => {
|
||||
if (cancelled) return;
|
||||
if (!data) {
|
||||
markDetailDegraded();
|
||||
return;
|
||||
}
|
||||
applySuccessfulHourlyDetail(data, { updateLiveTemp: true });
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
markDetailDegraded();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setIsHourlyLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
refreshActivatedCachedDetail();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
activationRefreshKey,
|
||||
city,
|
||||
compact,
|
||||
isActive,
|
||||
isMaximized,
|
||||
targetResolution,
|
||||
markDetailDegraded,
|
||||
markDetailRequest,
|
||||
applySuccessfulHourlyDetail,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldPollLiveChart({ city, compact, isActive, isMaximized })) return;
|
||||
let cancelled = false;
|
||||
@@ -1004,9 +1056,9 @@ export function LiveTemperatureThresholdChart({
|
||||
|
||||
lastForegroundRefreshAtRef.current = now;
|
||||
lastPatchAtRef.current = now;
|
||||
markDetailRequest("force_refresh");
|
||||
markDetailRequest("network");
|
||||
|
||||
fetchHourlyForecastForCity(city, { ignoreCache: true, resolution: targetResolution })
|
||||
fetchHourlyForecastForCity(city, { bypassLocalCache: true, resolution: targetResolution })
|
||||
.then((data) => {
|
||||
if (cancelled) return;
|
||||
if (!data) {
|
||||
|
||||
@@ -108,6 +108,20 @@ export async function runTests() {
|
||||
dashboardSource.includes('activeNavKey !== "thresholds"'),
|
||||
"terminal screen should preload the chart chunk once access is confirmed on the chart tab",
|
||||
);
|
||||
assert(
|
||||
dashboardSource.includes("terminalActivationRefreshKey") &&
|
||||
dashboardSource.includes("setTerminalActivationRefreshKey") &&
|
||||
querySource.includes("terminalActivationRefreshKey") &&
|
||||
querySource.includes("handleTerminalActivationRefresh") &&
|
||||
querySource.includes("fetchScanTerminal({ forceRefresh: false, showLoading: false })"),
|
||||
"switching back to the terminal tab should trigger a lightweight scan refresh without waiting for browser focus events",
|
||||
);
|
||||
assert(
|
||||
chartSource.includes("activationRefreshKey") &&
|
||||
chartSource.includes("refreshActivatedCachedDetail") &&
|
||||
chartSource.includes("fetchHourlyForecastForCity(city, { bypassLocalCache: true, resolution: targetResolution })"),
|
||||
"switching back to the terminal tab should refresh visible chart detail through cached backend data without forcing external sources",
|
||||
);
|
||||
assert(
|
||||
chartSource.includes("fetchHourlyForecastForCity(city, { ignoreCache: true, resolution: targetResolution })") &&
|
||||
chartSource.includes("setHourly((prev) => mergeHourlyWithLiveObservations(dataWithCurrentRow, prev, row))"),
|
||||
|
||||
@@ -249,11 +249,12 @@ export function runTests() {
|
||||
);
|
||||
const foregroundRefreshBlock = chart.match(/const refreshForegroundFullDetail = \(\) => \{[\s\S]*?\n \};/)?.[0] || "";
|
||||
assert(
|
||||
foregroundRefreshBlock.includes("ignoreCache: true") &&
|
||||
foregroundRefreshBlock.includes("fetchHourlyForecastForCity") &&
|
||||
foregroundRefreshBlock.includes("FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS") &&
|
||||
!foregroundRefreshBlock.includes("setIsHourlyLoading(true)"),
|
||||
"foreground resume refresh should update full detail in the background without showing the loading overlay or refetching fresh detail",
|
||||
foregroundRefreshBlock.includes("bypassLocalCache: true") &&
|
||||
foregroundRefreshBlock.includes("fetchHourlyForecastForCity") &&
|
||||
foregroundRefreshBlock.includes("FOREGROUND_FULL_DETAIL_REFRESH_DEDUP_MS") &&
|
||||
!foregroundRefreshBlock.includes("ignoreCache: true") &&
|
||||
!foregroundRefreshBlock.includes("setIsHourlyLoading(true)"),
|
||||
"foreground resume refresh should revalidate cached detail in the background without showing the loading overlay or forcing external sources",
|
||||
);
|
||||
assert(
|
||||
!chart.includes("/api/city/${encodeURIComponent(city)}/summary"),
|
||||
|
||||
@@ -85,11 +85,13 @@ function applyTerminalPatches(
|
||||
export function useScanTerminalQuery({
|
||||
isPro,
|
||||
proAccessLoading,
|
||||
terminalActivationRefreshKey = 0,
|
||||
timezoneOffsetSeconds,
|
||||
tradingRegion,
|
||||
}: {
|
||||
isPro: boolean;
|
||||
proAccessLoading: boolean;
|
||||
terminalActivationRefreshKey?: number;
|
||||
timezoneOffsetSeconds?: number | null;
|
||||
tradingRegion?: string;
|
||||
}) {
|
||||
@@ -206,6 +208,27 @@ export function useScanTerminalQuery({
|
||||
};
|
||||
}, [fetchScanTerminal, isPro, proAccessLoading, scanRemote.status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminalActivationRefreshKey) return;
|
||||
if (typeof document !== "undefined" && document.visibilityState !== "visible") return;
|
||||
if (proAccessLoading || !isPro || scanRemote.status === "loading") return;
|
||||
|
||||
const handleTerminalActivationRefresh = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastForegroundScanRefreshAtRef.current < 10_000) return;
|
||||
lastForegroundScanRefreshAtRef.current = now;
|
||||
void fetchScanTerminal({ forceRefresh: false, showLoading: false });
|
||||
};
|
||||
|
||||
handleTerminalActivationRefresh();
|
||||
}, [
|
||||
fetchScanTerminal,
|
||||
isPro,
|
||||
proAccessLoading,
|
||||
scanRemote.status,
|
||||
terminalActivationRefreshKey,
|
||||
]);
|
||||
|
||||
// Preload adjacent regions in idle time for instant tab switches
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !tradingRegion || !isPro) return;
|
||||
|
||||
@@ -110,8 +110,8 @@ def test_docker_compose_isolates_collector_from_web_and_bot_services():
|
||||
assert "POLYWEATHER_OBSERVATION_COLLECTOR_ENABLED: 'false'" in web_block
|
||||
assert "POLYWEATHER_OBSERVATION_COLLECTOR_ENABLED: 'true'" in collector_block
|
||||
assert "POLYWEATHER_OBSERVATION_COLLECTOR_ENABLED: 'false'" in warmer_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY:-2}" in web_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY:-2}" in web_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_CONCURRENCY:-3}" in web_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY: ${POLYWEATHER_CITY_DETAIL_BATCH_GLOBAL_CONCURRENCY:-3}" in web_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_QUEUE_WAIT_MS: ${POLYWEATHER_CITY_DETAIL_BATCH_QUEUE_WAIT_MS:-3000}" in web_block
|
||||
assert "POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS: ${POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS:-8000}" in web_block
|
||||
assert "UVICORN_WORKERS: ${UVICORN_WORKERS:-2}" in web_block
|
||||
|
||||
Reference in New Issue
Block a user