From b68dc5cf85a4b51d4c5110835766fa3c654417e0 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 7 May 2020 16:41:42 +0800 Subject: [PATCH] Fix false negative not found response --- .../src/components/TransactionDetails.tsx | 55 +++++++++-------- explorer/src/components/TransactionsCard.tsx | 61 +++++-------------- explorer/src/providers/cluster.tsx | 24 +++----- explorer/src/providers/transactions/index.tsx | 59 +++++++++++------- 4 files changed, 93 insertions(+), 106 deletions(-) diff --git a/explorer/src/components/TransactionDetails.tsx b/explorer/src/components/TransactionDetails.tsx index cbb9fed73d..d171a2269a 100644 --- a/explorer/src/components/TransactionDetails.tsx +++ b/explorer/src/components/TransactionDetails.tsx @@ -2,12 +2,10 @@ import React from "react"; import bs58 from "bs58"; import { Source, + useFetchTransactionStatus, useTransactionStatus, useTransactionDetails, - useTransactionsDispatch, useDetailsDispatch, - checkTransactionStatus, - ActionType, FetchStatus } from "../providers/transactions"; import { fetchDetails } from "providers/transactions/details"; @@ -28,8 +26,7 @@ import { useHistory, useLocation } from "react-router-dom"; type Props = { signature: TransactionSignature }; export default function TransactionDetails({ signature }: Props) { - const dispatch = useTransactionsDispatch(); - const { url } = useCluster(); + const fetchTransaction = useFetchTransactionStatus(); const [, setShow] = useClusterModal(); const [search, setSearch] = React.useState(signature); const history = useHistory(); @@ -41,13 +38,8 @@ export default function TransactionDetails({ signature }: Props) { // Fetch transaction on load React.useEffect(() => { - dispatch({ - type: ActionType.FetchSignature, - signature, - source: Source.Url - }); - checkTransactionStatus(dispatch, signature, url); - }, [signature, dispatch, url]); + fetchTransaction(signature, Source.Url); + }, [signature]); // eslint-disable-line react-hooks/exhaustive-deps const searchInput = ( { - checkTransactionStatus(dispatch, signature, url); - }; if (!status || status.fetchStatus === FetchStatus.Fetching) { return ; } else if (status?.fetchStatus === FetchStatus.FetchFailed) { - return ; + return refresh(signature)} text="Fetch Failed" />; } else if (!status.info) { - return ; + return refresh(signature)} text="Not Found" />; } const { info } = status; @@ -141,7 +128,10 @@ function StatusCard({ signature }: Props) {

Status

- @@ -179,14 +169,25 @@ function AccountsCard({ signature }: Props) { const dispatch = useDetailsDispatch(); const { url } = useCluster(); + const fetchStatus = useFetchTransactionStatus(); + const refreshStatus = () => fetchStatus(signature); const refreshDetails = () => fetchDetails(dispatch, signature, url); const transaction = details?.transaction?.transaction; const message = React.useMemo(() => { return transaction?.compileMessage(); }, [transaction]); - if (!details) { + const status = useTransactionStatus(signature); + + if (!status || !status.info) { return null; + } else if (!details) { + return ( + + ); } else if (details.fetchStatus === FetchStatus.Fetching) { return ; } else if (details?.fetchStatus === FetchStatus.FetchFailed) { @@ -571,11 +572,17 @@ function LoadingCard() { function RetryCard({ retry, text }: { retry: () => void; text: string }) { return (
-
+
{text} - + Try Again +
+
+ + Try Again + +
); diff --git a/explorer/src/components/TransactionsCard.tsx b/explorer/src/components/TransactionsCard.tsx index 1e3e9fad9e..6218378d09 100644 --- a/explorer/src/components/TransactionsCard.tsx +++ b/explorer/src/components/TransactionsCard.tsx @@ -2,24 +2,20 @@ import React from "react"; import { Link } from "react-router-dom"; import { useTransactions, - useTransactionsDispatch, - checkTransactionStatus, - ActionType, TransactionStatus, Source, - FetchStatus + FetchStatus, + useFetchTransactionStatus } from "../providers/transactions"; import bs58 from "bs58"; import { assertUnreachable } from "../utils"; -import { useCluster } from "../providers/cluster"; import Copyable from "./Copyable"; function TransactionsCard() { const { transactions, idCounter } = useTransactions(); - const dispatch = useTransactionsDispatch(); + const fetchTransaction = useFetchTransactionStatus(); const signatureInput = React.useRef(null); const [error, setError] = React.useState(""); - const { url } = useCluster(); const onNew = (signature: string) => { if (signature.length === 0) return; @@ -37,13 +33,7 @@ function TransactionsCard() { return; } - dispatch({ - type: ActionType.FetchSignature, - signature, - source: Source.Input - }); - checkTransactionStatus(dispatch, signature, url); - + fetchTransaction(signature, Source.Input); const inputEl = signatureInput.current; if (inputEl) { inputEl.value = ""; @@ -98,9 +88,7 @@ function TransactionsCard() { - - {transactions.map(transaction => - renderTransactionRow(transaction, dispatch, url) - )} + {transactions.map(transaction => renderTransactionRow(transaction))}
@@ -120,11 +108,7 @@ const renderHeader = () => { ); }; -const renderTransactionRow = ( - transactionStatus: TransactionStatus, - dispatch: any, - url: string -) => { +const renderTransactionRow = (transactionStatus: TransactionStatus) => { const { fetchStatus, info, signature, id } = transactionStatus; let statusText; @@ -162,30 +146,6 @@ const renderTransactionRow = ( confirmationsText = `${info.confirmations}`; } - const renderDetails = () => { - if (info?.confirmations === "max") { - return ( - ({ ...location, pathname: "/tx/" + signature })} - className="btn btn-rounded-circle btn-white btn-sm" - > - - - ); - } else { - return ( - - ); - } - }; - return ( @@ -201,7 +161,14 @@ const renderTransactionRow = ( {confirmationsText} {slotText} - {renderDetails()} + + ({ ...location, pathname: "/tx/" + signature })} + className="btn btn-rounded-circle btn-white btn-sm" + > + + + ); }; diff --git a/explorer/src/providers/cluster.tsx b/explorer/src/providers/cluster.tsx index 0083f71ec5..6225ea2926 100644 --- a/explorer/src/providers/cluster.tsx +++ b/explorer/src/providers/cluster.tsx @@ -61,28 +61,24 @@ interface State { status: ClusterStatus; } -interface Connecting { - status: ClusterStatus.Connecting; +interface Action { + status: ClusterStatus; cluster: Cluster; customUrl: string; } -interface Connected { - status: ClusterStatus.Connected; -} - -interface Failure { - status: ClusterStatus.Failure; -} - -type Action = Connected | Connecting | Failure; type Dispatch = (action: Action) => void; function clusterReducer(state: State, action: Action): State { switch (action.status) { case ClusterStatus.Connected: case ClusterStatus.Failure: { - return Object.assign({}, state, { status: action.status }); + if ( + state.cluster !== action.cluster || + state.customUrl !== action.customUrl + ) + return state; + return action; } case ClusterStatus.Connecting: { return action; @@ -197,10 +193,10 @@ async function updateCluster( try { const connection = new Connection(clusterUrl(cluster, customUrl)); await connection.getRecentBlockhash(); - dispatch({ status: ClusterStatus.Connected }); + dispatch({ status: ClusterStatus.Connected, cluster, customUrl }); } catch (error) { console.error("Failed to update cluster", error); - dispatch({ status: ClusterStatus.Failure }); + dispatch({ status: ClusterStatus.Failure, cluster, customUrl }); } } diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 7f7e625f27..7827235f2f 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -7,7 +7,7 @@ import { SignatureResult } from "@solana/web3.js"; import { useQuery } from "../../utils/url"; -import { useCluster, Cluster } from "../cluster"; +import { useCluster, Cluster, ClusterStatus } from "../cluster"; import { DetailsProvider, StateContext as DetailsStateContext, @@ -127,7 +127,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { transactions: {} }); - const { cluster, url } = useCluster(); + const { cluster, status: clusterStatus, url } = useCluster(); const accountsDispatch = useAccountsDispatch(); const query = useQuery(); const testFlag = query.get("test"); @@ -140,14 +140,14 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { signature, source: Source.Url }); - checkTransactionStatus(dispatch, signature, url); + fetchTransactionStatus(dispatch, signature, url, clusterStatus); }); // Create a test transaction if (cluster === Cluster.Devnet && testFlag !== null) { - createTestTransaction(dispatch, accountsDispatch, url); + createTestTransaction(dispatch, accountsDispatch, url, clusterStatus); } - }, [testFlag, cluster, url]); // eslint-disable-line react-hooks/exhaustive-deps + }, [testFlag, cluster, clusterStatus, url]); // eslint-disable-line react-hooks/exhaustive-deps // Check for transactions in the url params const values = TX_ALIASES.flatMap(key => [ @@ -167,7 +167,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { signature, source: Source.Url }); - checkTransactionStatus(dispatch, signature, url); + fetchTransactionStatus(dispatch, signature, url, clusterStatus); }); }, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps @@ -183,7 +183,8 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) { async function createTestTransaction( dispatch: Dispatch, accountsDispatch: AccountsDispatch, - url: string + url: string, + clusterStatus: ClusterStatus ) { const testKey = process.env.REACT_APP_TEST_KEY; let testAccount = new Account(); @@ -203,7 +204,7 @@ async function createTestTransaction( signature, source: Source.Test }); - checkTransactionStatus(dispatch, signature, url); + fetchTransactionStatus(dispatch, signature, url, clusterStatus); accountsDispatch({ type: AccountsActionType.Input, pubkey: testAccount.publicKey @@ -226,16 +227,17 @@ async function createTestTransaction( signature, source: Source.Test }); - checkTransactionStatus(dispatch, signature, url); + fetchTransactionStatus(dispatch, signature, url, clusterStatus); } catch (error) { console.error("Failed to create test failure transaction", error); } } -export async function checkTransactionStatus( +export async function fetchTransactionStatus( dispatch: Dispatch, signature: TransactionSignature, - url: string + url: string, + status: ClusterStatus ) { dispatch({ type: ActionType.UpdateStatus, @@ -243,6 +245,9 @@ export async function checkTransactionStatus( fetchStatus: FetchStatus.Fetching }); + // We will auto-refetch when status is no longer connecting + if (status === ClusterStatus.Connecting) return; + let fetchStatus; let info: TransactionStatusInfo | undefined; try { @@ -317,16 +322,6 @@ export function useTransactionDetails(signature: TransactionSignature) { return context[signature]; } -export function useTransactionsDispatch() { - const context = React.useContext(DispatchContext); - if (!context) { - throw new Error( - `useTransactionsDispatch must be used within a TransactionsProvider` - ); - } - return context; -} - export function useDetailsDispatch() { const context = React.useContext(DetailsDispatchContext); if (!context) { @@ -336,3 +331,25 @@ export function useDetailsDispatch() { } return context; } + +export function useFetchTransactionStatus() { + const dispatch = React.useContext(DispatchContext); + if (!dispatch) { + throw new Error( + `useFetchTransactionStatus must be used within a TransactionsProvider` + ); + } + + const { url, status } = useCluster(); + return (signature: TransactionSignature, source?: Source) => { + if (source !== undefined) { + dispatch({ + type: ActionType.FetchSignature, + signature, + source + }); + } + + fetchTransactionStatus(dispatch, signature, url, status); + }; +}