From 7b299ca6a39dee7f985c9e807eba263d528c4417 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Thu, 20 Aug 2020 13:30:12 -0700 Subject: [PATCH 01/18] explorer: Auto-update transactions until they reach max confirmation --- explorer/src/pages/TransactionDetailsPage.tsx | 31 ++++++++++++++++--- explorer/src/providers/cache.tsx | 3 ++ explorer/src/providers/transactions/index.tsx | 9 ++++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 8079c13f13b..d9a4859aca5 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -29,6 +29,8 @@ import { intoTransactionInstruction } from "utils/tx"; import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard"; import { FetchStatus } from "providers/cache"; +const AUTO_REFRESH_TIMEOUT = 2000; + type Props = { signature: TransactionSignature }; export function TransactionDetailsPage({ signature: raw }: Props) { let signature: TransactionSignature | undefined; @@ -67,9 +69,10 @@ function StatusCard({ signature }: Props) { const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); + const [isAutoRefresh, setIsAutoRefresh] = React.useState(false); const refresh = React.useCallback( - (signature: string) => { - fetchStatus(signature); + (signature: string, isAutoRefresh: boolean = false) => { + fetchStatus(signature, isAutoRefresh); fetchDetails(signature); }, [fetchStatus, fetchDetails] @@ -82,7 +85,10 @@ function StatusCard({ signature }: Props) { } }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps - if (!status || status.status === FetchStatus.Fetching) { + if ( + !status || + (status.status === FetchStatus.Fetching && !status.isAutoRefresh) + ) { return ; } else if (status.status === FetchStatus.FetchFailed) { return ( @@ -102,6 +108,15 @@ function StatusCard({ signature }: Props) { } const { info } = status.data; + + if (!isAutoRefresh && info.confirmations !== "max") { + setIsAutoRefresh(true); + setTimeout(() => { + refresh(signature, true); + setIsAutoRefresh(false); + }, AUTO_REFRESH_TIMEOUT); + } + const renderResult = () => { let statusClass = "success"; let statusText = "Success"; @@ -231,14 +246,20 @@ function AccountsCard({ signature }: Props) { if (!status?.data?.info) { return null; - } else if (!details) { + } else if ( + !details || + (status.isAutoRefresh && status.data.info.confirmations !== "max") + ) { return ( ); - } else if (details.status === FetchStatus.Fetching) { + } else if ( + details.status === FetchStatus.Fetching && + !details.isAutoRefresh + ) { return ; } else if (details.status === FetchStatus.FetchFailed) { return ; diff --git a/explorer/src/providers/cache.tsx b/explorer/src/providers/cache.tsx index 27484166ce2..ccd18245b4a 100644 --- a/explorer/src/providers/cache.tsx +++ b/explorer/src/providers/cache.tsx @@ -9,6 +9,7 @@ export enum FetchStatus { export type CacheEntry = { status: FetchStatus; data?: T; + isAutoRefresh?: boolean; }; export type State = { @@ -29,6 +30,7 @@ export type Update = { key: string; status: FetchStatus; data?: T; + isAutoRefresh?: boolean; }; export type Clear = { @@ -100,6 +102,7 @@ export function reducer( ...entry, status: action.status, data: reconciler(entry?.data, action.data), + isAutoRefresh: action.isAutoRefresh, }, }; return { ...state, entries }; diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 7782e2772bf..2e7b7c857e9 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -56,13 +56,15 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { export async function fetchTransactionStatus( dispatch: Dispatch, signature: TransactionSignature, - url: string + url: string, + isAutoRefresh: boolean ) { dispatch({ type: ActionType.Update, key: signature, status: FetchStatus.Fetching, url, + isAutoRefresh, }); let fetchStatus; @@ -117,6 +119,7 @@ export async function fetchTransactionStatus( status: fetchStatus, data, url, + isAutoRefresh, }); } @@ -153,7 +156,7 @@ export function useFetchTransactionStatus() { } const { url } = useCluster(); - return (signature: TransactionSignature) => { - fetchTransactionStatus(dispatch, signature, url); + return (signature: TransactionSignature, isAutoRefresh = false) => { + fetchTransactionStatus(dispatch, signature, url, isAutoRefresh); }; } From 2ede5a6697638df4cc00954ff6a42d0b46bdd41f Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Thu, 20 Aug 2020 13:59:56 -0700 Subject: [PATCH 02/18] convert to side effect --- explorer/src/pages/TransactionDetailsPage.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index d9a4859aca5..44e67d197f1 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -85,6 +85,16 @@ function StatusCard({ signature }: Props) { } }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps + React.useEffect(() => { + if (!isAutoRefresh && status?.data?.info?.confirmations !== "max") { + setIsAutoRefresh(true); + setTimeout(() => { + refresh(signature, true); + setIsAutoRefresh(false); + }, AUTO_REFRESH_TIMEOUT); + } + }, [isAutoRefresh, status, refresh, signature]); + if ( !status || (status.status === FetchStatus.Fetching && !status.isAutoRefresh) @@ -109,14 +119,6 @@ function StatusCard({ signature }: Props) { const { info } = status.data; - if (!isAutoRefresh && info.confirmations !== "max") { - setIsAutoRefresh(true); - setTimeout(() => { - refresh(signature, true); - setIsAutoRefresh(false); - }, AUTO_REFRESH_TIMEOUT); - } - const renderResult = () => { let statusClass = "success"; let statusText = "Success"; From 0d8cdcf85e727ba735ef570c4149b63f59f2753a Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Thu, 20 Aug 2020 15:32:12 -0700 Subject: [PATCH 03/18] proper cleanup --- explorer/src/pages/TransactionDetailsPage.tsx | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 44e67d197f1..45d13f06f3d 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -31,6 +31,11 @@ import { FetchStatus } from "providers/cache"; const AUTO_REFRESH_TIMEOUT = 2000; +type AutoRefresh = { + isAutoRefresh: boolean; + timeout?: NodeJS.Timeout | false; +}; + type Props = { signature: TransactionSignature }; export function TransactionDetailsPage({ signature: raw }: Props) { let signature: TransactionSignature | undefined; @@ -69,7 +74,10 @@ function StatusCard({ signature }: Props) { const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); - const [isAutoRefresh, setIsAutoRefresh] = React.useState(false); + const [autoRefresh, setAutoRefresh] = React.useState({ + isAutoRefresh: false, + }); + const refresh = React.useCallback( (signature: string, isAutoRefresh: boolean = false) => { fetchStatus(signature, isAutoRefresh); @@ -86,14 +94,32 @@ function StatusCard({ signature }: Props) { }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps React.useEffect(() => { - if (!isAutoRefresh && status?.data?.info?.confirmations !== "max") { - setIsAutoRefresh(true); - setTimeout(() => { + return () => { + if (autoRefresh.timeout) { + clearTimeout(autoRefresh.timeout); + } + }; + }, [autoRefresh]); + + React.useEffect(() => { + if ( + !autoRefresh.isAutoRefresh && + status?.data?.info?.confirmations !== "max" + ) { + let timeout = setTimeout(() => { refresh(signature, true); - setIsAutoRefresh(false); + setAutoRefresh({ + isAutoRefresh: false, + timeout: false, + }); }, AUTO_REFRESH_TIMEOUT); + + setAutoRefresh({ + isAutoRefresh: true, + timeout: timeout, + }); } - }, [isAutoRefresh, status, refresh, signature]); + }, [autoRefresh, status, refresh, signature]); if ( !status || From 5056b47fef0e22f0533ba38da1ffc19a533835e0 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Thu, 20 Aug 2020 17:20:49 -0700 Subject: [PATCH 04/18] minor cleanup --- explorer/src/pages/TransactionDetailsPage.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 45d13f06f3d..5d1f121a97b 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -106,17 +106,15 @@ function StatusCard({ signature }: Props) { !autoRefresh.isAutoRefresh && status?.data?.info?.confirmations !== "max" ) { - let timeout = setTimeout(() => { - refresh(signature, true); - setAutoRefresh({ - isAutoRefresh: false, - timeout: false, - }); - }, AUTO_REFRESH_TIMEOUT); - setAutoRefresh({ isAutoRefresh: true, - timeout: timeout, + timeout: setTimeout(() => { + refresh(signature, true); + setAutoRefresh({ + isAutoRefresh: false, + timeout: false, + }); + }, AUTO_REFRESH_TIMEOUT), }); } }, [autoRefresh, status, refresh, signature]); From 400b1a81755174ee6fd47411ddc2a5a92ee45af9 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Fri, 21 Aug 2020 12:20:54 -0700 Subject: [PATCH 05/18] pull isAutoRefresh from context, refactor, and add loading indicator / dhide refresh --- explorer/src/pages/TransactionDetailsPage.tsx | 97 ++++++++++--------- explorer/src/providers/cache.tsx | 3 - explorer/src/providers/transactions/index.tsx | 9 +- 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 5d1f121a97b..bd8c4252efd 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -31,11 +31,6 @@ import { FetchStatus } from "providers/cache"; const AUTO_REFRESH_TIMEOUT = 2000; -type AutoRefresh = { - isAutoRefresh: boolean; - timeout?: NodeJS.Timeout | false; -}; - type Props = { signature: TransactionSignature }; export function TransactionDetailsPage({ signature: raw }: Props) { let signature: TransactionSignature | undefined; @@ -74,18 +69,27 @@ function StatusCard({ signature }: Props) { const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); - const [autoRefresh, setAutoRefresh] = React.useState({ - isAutoRefresh: false, - }); + const autoRefreshTimeout = React.useRef(null); + const [autoRefreshInProcess, setAutoRefreshInProcess] = React.useState< + boolean + >(false); const refresh = React.useCallback( - (signature: string, isAutoRefresh: boolean = false) => { - fetchStatus(signature, isAutoRefresh); + (signature: string) => { + fetchStatus(signature); fetchDetails(signature); }, [fetchStatus, fetchDetails] ); + React.useEffect(() => { + return () => { + if (autoRefreshTimeout.current) { + clearTimeout(autoRefreshTimeout.current); + } + }; + }, []); + // Fetch transaction on load React.useEffect(() => { if (!status && clusterStatus === ClusterStatus.Connected) { @@ -94,34 +98,37 @@ function StatusCard({ signature }: Props) { }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps React.useEffect(() => { - return () => { - if (autoRefresh.timeout) { - clearTimeout(autoRefresh.timeout); - } - }; - }, [autoRefresh]); + if (!status || !status.data?.info) { + return; + } + + if (status.status !== FetchStatus.Fetching && autoRefreshInProcess) { + setAutoRefreshInProcess(false); + } - React.useEffect(() => { if ( - !autoRefresh.isAutoRefresh && - status?.data?.info?.confirmations !== "max" + !autoRefreshTimeout.current && + status.data.info.confirmations !== "max" ) { - setAutoRefresh({ - isAutoRefresh: true, - timeout: setTimeout(() => { - refresh(signature, true); - setAutoRefresh({ - isAutoRefresh: false, - timeout: false, - }); - }, AUTO_REFRESH_TIMEOUT), - }); + autoRefreshTimeout.current = setTimeout(() => { + autoRefreshTimeout.current = null; + refresh(signature); + setAutoRefreshInProcess(true); + }, AUTO_REFRESH_TIMEOUT); } - }, [autoRefresh, status, refresh, signature]); + + if ( + autoRefreshTimeout.current && + status.data.info.confirmations === "max" + ) { + clearTimeout(autoRefreshTimeout.current); + autoRefreshTimeout.current = null; + } + }, [autoRefreshInProcess, status, refresh, signature]); if ( !status || - (status.status === FetchStatus.Fetching && !status.isAutoRefresh) + (status.status === FetchStatus.Fetching && !autoRefreshInProcess) ) { return ; } else if (status.status === FetchStatus.FetchFailed) { @@ -175,13 +182,17 @@ function StatusCard({ signature }: Props) {

Overview

- + {status.data.info.confirmations === "max" ? ( + + ) : ( + + )}
@@ -272,20 +283,14 @@ function AccountsCard({ signature }: Props) { if (!status?.data?.info) { return null; - } else if ( - !details || - (status.isAutoRefresh && status.data.info.confirmations !== "max") - ) { + } else if (!details || status.data.info.confirmations !== "max") { return ( ); - } else if ( - details.status === FetchStatus.Fetching && - !details.isAutoRefresh - ) { + } else if (details.status === FetchStatus.Fetching) { return ; } else if (details.status === FetchStatus.FetchFailed) { return ; diff --git a/explorer/src/providers/cache.tsx b/explorer/src/providers/cache.tsx index ccd18245b4a..27484166ce2 100644 --- a/explorer/src/providers/cache.tsx +++ b/explorer/src/providers/cache.tsx @@ -9,7 +9,6 @@ export enum FetchStatus { export type CacheEntry = { status: FetchStatus; data?: T; - isAutoRefresh?: boolean; }; export type State = { @@ -30,7 +29,6 @@ export type Update = { key: string; status: FetchStatus; data?: T; - isAutoRefresh?: boolean; }; export type Clear = { @@ -102,7 +100,6 @@ export function reducer( ...entry, status: action.status, data: reconciler(entry?.data, action.data), - isAutoRefresh: action.isAutoRefresh, }, }; return { ...state, entries }; diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 2e7b7c857e9..7782e2772bf 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -56,15 +56,13 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { export async function fetchTransactionStatus( dispatch: Dispatch, signature: TransactionSignature, - url: string, - isAutoRefresh: boolean + url: string ) { dispatch({ type: ActionType.Update, key: signature, status: FetchStatus.Fetching, url, - isAutoRefresh, }); let fetchStatus; @@ -119,7 +117,6 @@ export async function fetchTransactionStatus( status: fetchStatus, data, url, - isAutoRefresh, }); } @@ -156,7 +153,7 @@ export function useFetchTransactionStatus() { } const { url } = useCluster(); - return (signature: TransactionSignature, isAutoRefresh = false) => { - fetchTransactionStatus(dispatch, signature, url, isAutoRefresh); + return (signature: TransactionSignature) => { + fetchTransactionStatus(dispatch, signature, url); }; } From e3feb7d5aa4a667d7a0f1e64fecc288d2b146d49 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Mon, 24 Aug 2020 20:40:59 -0700 Subject: [PATCH 06/18] split effects into two, manage interval in one effect only --- explorer/src/pages/TransactionDetailsPage.tsx | 59 ++++++------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index bd8c4252efd..480108502a2 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -66,10 +66,8 @@ export function TransactionDetailsPage({ signature: raw }: Props) { function StatusCard({ signature }: Props) { const fetchStatus = useFetchTransactionStatus(); const status = useTransactionStatus(signature); - const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); - const autoRefreshTimeout = React.useRef(null); const [autoRefreshInProcess, setAutoRefreshInProcess] = React.useState< boolean >(false); @@ -77,19 +75,10 @@ function StatusCard({ signature }: Props) { const refresh = React.useCallback( (signature: string) => { fetchStatus(signature); - fetchDetails(signature); }, - [fetchStatus, fetchDetails] + [fetchStatus] ); - React.useEffect(() => { - return () => { - if (autoRefreshTimeout.current) { - clearTimeout(autoRefreshTimeout.current); - } - }; - }, []); - // Fetch transaction on load React.useEffect(() => { if (!status && clusterStatus === ClusterStatus.Connected) { @@ -97,34 +86,29 @@ function StatusCard({ signature }: Props) { } }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps + // Manage auto-refresh React.useEffect(() => { if (!status || !status.data?.info) { return; - } - - if (status.status !== FetchStatus.Fetching && autoRefreshInProcess) { + } else if (status.data.info.confirmations !== "max" && !autoRefreshInProcess) { + setAutoRefreshInProcess(true); + } else if (status.data.info.confirmations === 'max' && autoRefreshInProcess) { setAutoRefreshInProcess(false); } + }, [status, autoRefreshInProcess]); - if ( - !autoRefreshTimeout.current && - status.data.info.confirmations !== "max" - ) { - autoRefreshTimeout.current = setTimeout(() => { - autoRefreshTimeout.current = null; - refresh(signature); - setAutoRefreshInProcess(true); + // Effect to set and clear interval for auto-refresh + React.useEffect(() => { + if (autoRefreshInProcess) { + let intervalHandle: NodeJS.Timeout = setInterval(() => { + fetchStatus(signature); }, AUTO_REFRESH_TIMEOUT); - } - if ( - autoRefreshTimeout.current && - status.data.info.confirmations === "max" - ) { - clearTimeout(autoRefreshTimeout.current); - autoRefreshTimeout.current = null; + return () => { + clearInterval(intervalHandle); + }; } - }, [autoRefreshInProcess, status, refresh, signature]); + }, [autoRefreshInProcess, fetchStatus, signature]); if ( !status || @@ -182,7 +166,7 @@ function StatusCard({ signature }: Props) {

Overview

- {status.data.info.confirmations === "max" ? ( + {!autoRefreshInProcess ? ( - ) : ( - )}
From 824b0d622c1534935aa0b084df2d3b0f85c77775 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Tue, 25 Aug 2020 00:53:27 -0700 Subject: [PATCH 10/18] accidentally factored out not found case --- explorer/src/pages/TransactionDetailsPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index e0b7916d6e6..29a7d8e0d99 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -47,8 +47,9 @@ export function TransactionDetailsPage({ signature: raw }: Props) { const status = useTransactionStatus(signature); - const autoRefreshInProcess = !( - status?.data?.info && status.data.info.confirmations === "max" + const autoRefreshInProcess = !!( + status?.data?.info && + status.data.info.confirmations !== "max" ); return ( From 35a25581e987bd31777094d77fc2f78bcfd49f6e Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Tue, 25 Aug 2020 10:12:35 -0700 Subject: [PATCH 11/18] add attempts bailout --- explorer/src/pages/TransactionDetailsPage.tsx | 36 ++++++++++++++----- explorer/src/providers/cache.tsx | 12 ++++++- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 29a7d8e0d99..6dd0847b98d 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -30,12 +30,17 @@ import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard" import { FetchStatus } from "providers/cache"; const AUTO_REFRESH_TIMEOUT = 2000; +const ZERO_CONFIRMATION_BAILOUT = 20; -type Props = { +type SignatureProps = { signature: TransactionSignature; - autoRefreshInProcess?: boolean; }; -export function TransactionDetailsPage({ signature: raw }: Props) { + +type AutoRefreshProps = { + autoRefreshInProcess: boolean; +}; + +export function TransactionDetailsPage({ signature: raw }: SignatureProps) { let signature: TransactionSignature | undefined; try { @@ -47,10 +52,17 @@ export function TransactionDetailsPage({ signature: raw }: Props) { const status = useTransactionStatus(signature); - const autoRefreshInProcess = !!( + let autoRefreshInProcess = false; + + if ( status?.data?.info && - status.data.info.confirmations !== "max" - ); + status.data.info.confirmations === 0 && + status.attempts > ZERO_CONFIRMATION_BAILOUT + ) { + autoRefreshInProcess = false; + } else if (status?.data?.info && status.data.info.confirmations !== "max") { + autoRefreshInProcess = true; + } return (
@@ -79,7 +91,10 @@ export function TransactionDetailsPage({ signature: raw }: Props) { ); } -function StatusCard({ signature, autoRefreshInProcess }: Props) { +function StatusCard({ + signature, + autoRefreshInProcess, +}: SignatureProps & AutoRefreshProps) { const fetchStatus = useFetchTransactionStatus(); const status = useTransactionStatus(signature); const details = useTransactionDetails(signature); @@ -243,7 +258,10 @@ function StatusCard({ signature, autoRefreshInProcess }: Props) { ); } -function AccountsCard({ signature, autoRefreshInProcess }: Props) { +function AccountsCard({ + signature, + autoRefreshInProcess, +}: SignatureProps & AutoRefreshProps) { const { url } = useCluster(); const details = useTransactionDetails(signature); const fetchDetails = useFetchTransactionDetails(); @@ -344,7 +362,7 @@ function AccountsCard({ signature, autoRefreshInProcess }: Props) { ); } -function InstructionsSection({ signature }: Props) { +function InstructionsSection({ signature }: SignatureProps) { const status = useTransactionStatus(signature); const details = useTransactionDetails(signature); const fetchDetails = useFetchTransactionDetails(); diff --git a/explorer/src/providers/cache.tsx b/explorer/src/providers/cache.tsx index 27484166ce2..eb86cf1003a 100644 --- a/explorer/src/providers/cache.tsx +++ b/explorer/src/providers/cache.tsx @@ -9,6 +9,7 @@ export enum FetchStatus { export type CacheEntry = { status: FetchStatus; data?: T; + attempts: number; }; export type State = { @@ -94,12 +95,21 @@ export function reducer( case ActionType.Update: { const key = action.key; const entry = state.entries[key]; + + if (entry && entry.attempts === undefined) { + entry.attempts = 0; + } + + if (action.status === FetchStatus.Fetched) { + entry.attempts++; + } + const entries = { ...state.entries, [key]: { ...entry, status: action.status, - data: reconciler(entry?.data, action.data), + data: reconciler(entry?.data, action.data) }, }; return { ...state, entries }; From ce527944758d1db7f83d0101e63afc3370ce34cb Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Tue, 25 Aug 2020 11:29:40 -0700 Subject: [PATCH 12/18] run prettier --- explorer/src/providers/cache.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/src/providers/cache.tsx b/explorer/src/providers/cache.tsx index eb86cf1003a..b73834b2e53 100644 --- a/explorer/src/providers/cache.tsx +++ b/explorer/src/providers/cache.tsx @@ -109,7 +109,7 @@ export function reducer( [key]: { ...entry, status: action.status, - data: reconciler(entry?.data, action.data) + data: reconciler(entry?.data, action.data), }, }; return { ...state, entries }; From db27f14d092cce5677baad9c0a4962ec8784cda3 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Tue, 25 Aug 2020 11:35:24 -0700 Subject: [PATCH 13/18] bailout after 5 polls of 0 confirmations --- explorer/src/pages/TransactionDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 6dd0847b98d..b1482adab89 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -30,7 +30,7 @@ import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard" import { FetchStatus } from "providers/cache"; const AUTO_REFRESH_TIMEOUT = 2000; -const ZERO_CONFIRMATION_BAILOUT = 20; +const ZERO_CONFIRMATION_BAILOUT = 5; type SignatureProps = { signature: TransactionSignature; From 42d07b2528539976c5a89d743723ad9bcfebd035 Mon Sep 17 00:00:00 2001 From: Josh Hundley Date: Wed, 26 Aug 2020 11:25:14 -0700 Subject: [PATCH 14/18] move bailout into state, change autoRefresh prop to enum to support bailout state --- explorer/src/pages/TransactionDetailsPage.tsx | 56 ++++++++++++------- explorer/src/providers/cache.tsx | 10 ---- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index b1482adab89..95b6b213c2c 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -29,15 +29,21 @@ import { intoTransactionInstruction } from "utils/tx"; import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard"; import { FetchStatus } from "providers/cache"; -const AUTO_REFRESH_TIMEOUT = 2000; +const AUTO_REFRESH_INTERVAL = 2000; const ZERO_CONFIRMATION_BAILOUT = 5; type SignatureProps = { signature: TransactionSignature; }; +enum AutoRefresh { + Active, + Inactive, + BailedOut +}; + type AutoRefreshProps = { - autoRefreshInProcess: boolean; + autoRefresh: AutoRefresh; }; export function TransactionDetailsPage({ signature: raw }: SignatureProps) { @@ -51,17 +57,27 @@ export function TransactionDetailsPage({ signature: raw }: SignatureProps) { } catch (err) {} const status = useTransactionStatus(signature); + const [zeroConfirmationRetries, setZeroConfirmationRetries] = React.useState(0); - let autoRefreshInProcess = false; + React.useEffect(() => { + if ( + status && + status.status === FetchStatus.Fetched && + status.data?.info && status.data.info.confirmations === 0 + ) { + setZeroConfirmationRetries(retries => retries + 1); + } + }, [status]); - if ( + let autoRefresh = AutoRefresh.Inactive; + + if (zeroConfirmationRetries >= ZERO_CONFIRMATION_BAILOUT) { + autoRefresh = AutoRefresh.BailedOut; + } else if ( status?.data?.info && - status.data.info.confirmations === 0 && - status.attempts > ZERO_CONFIRMATION_BAILOUT + status.data.info.confirmations !== "max" ) { - autoRefreshInProcess = false; - } else if (status?.data?.info && status.data.info.confirmations !== "max") { - autoRefreshInProcess = true; + autoRefresh = AutoRefresh.Active; } return ( @@ -78,11 +94,11 @@ export function TransactionDetailsPage({ signature: raw }: SignatureProps) { <> @@ -93,7 +109,7 @@ export function TransactionDetailsPage({ signature: raw }: SignatureProps) { function StatusCard({ signature, - autoRefreshInProcess, + autoRefresh, }: SignatureProps & AutoRefreshProps) { const fetchStatus = useFetchTransactionStatus(); const status = useTransactionStatus(signature); @@ -109,21 +125,21 @@ function StatusCard({ // Effect to set and clear interval for auto-refresh React.useEffect(() => { - if (autoRefreshInProcess) { + if (autoRefresh === AutoRefresh.Active) { let intervalHandle: NodeJS.Timeout = setInterval( () => fetchStatus(signature), - AUTO_REFRESH_TIMEOUT + AUTO_REFRESH_INTERVAL ); return () => { clearInterval(intervalHandle); }; } - }, [autoRefreshInProcess, fetchStatus, signature]); + }, [autoRefresh, fetchStatus, signature]); if ( !status || - (status.status === FetchStatus.Fetching && !autoRefreshInProcess) + (status.status === FetchStatus.Fetching && autoRefresh === AutoRefresh.Inactive) ) { return ; } else if (status.status === FetchStatus.FetchFailed) { @@ -177,7 +193,7 @@ function StatusCard({

Overview

- {autoRefreshInProcess ? ( + {autoRefresh === AutoRefresh.Active ? ( ) : (