Explorer: fix slot links and block details page on devnet (#13274)
This commit is contained in:
parent
c74c565d25
commit
0f05e086fe
|
@ -6,32 +6,31 @@ import { Signature } from "components/common/Signature";
|
||||||
import { ErrorCard } from "components/common/ErrorCard";
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
import { LoadingCard } from "components/common/LoadingCard";
|
import { LoadingCard } from "components/common/LoadingCard";
|
||||||
import { Slot } from "components/common/Slot";
|
import { Slot } from "components/common/Slot";
|
||||||
|
import { ClusterStatus, useCluster } from "providers/cluster";
|
||||||
|
|
||||||
export function BlockHistoryCard({ slot }: { slot: number }) {
|
export function BlockHistoryCard({ slot }: { slot: number }) {
|
||||||
const confirmedBlock = useBlock(slot);
|
const confirmedBlock = useBlock(slot);
|
||||||
const fetchBlock = useFetchBlock();
|
const fetchBlock = useFetchBlock();
|
||||||
|
const { status } = useCluster();
|
||||||
const refresh = () => fetchBlock(slot);
|
const refresh = () => fetchBlock(slot);
|
||||||
|
|
||||||
|
// Fetch block on load
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!confirmedBlock) refresh();
|
if (!confirmedBlock && status === ClusterStatus.Connected) refresh();
|
||||||
}, [confirmedBlock, slot]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [slot, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
if (!confirmedBlock) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmedBlock.data === undefined) {
|
|
||||||
if (confirmedBlock.status === FetchStatus.Fetching) {
|
|
||||||
return <LoadingCard message="Loading block" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!confirmedBlock || confirmedBlock.status === FetchStatus.Fetching) {
|
||||||
|
return <LoadingCard message="Loading block" />;
|
||||||
|
} else if (
|
||||||
|
confirmedBlock.data === undefined ||
|
||||||
|
confirmedBlock.status === FetchStatus.FetchFailed
|
||||||
|
) {
|
||||||
return <ErrorCard retry={refresh} text="Failed to fetch block" />;
|
return <ErrorCard retry={refresh} text="Failed to fetch block" />;
|
||||||
|
} else if (confirmedBlock.data.block === undefined) {
|
||||||
|
return <ErrorCard retry={refresh} text={`Block ${slot} was not found`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirmedBlock.status === FetchStatus.FetchFailed) {
|
const block = confirmedBlock.data.block;
|
||||||
return <ErrorCard retry={refresh} text="Failed to fetch block" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
|
@ -44,31 +43,31 @@ export function BlockHistoryCard({ slot }: { slot: number }) {
|
||||||
<tr>
|
<tr>
|
||||||
<td className="w-100">Slot</td>
|
<td className="w-100">Slot</td>
|
||||||
<td className="text-lg-right text-monospace">
|
<td className="text-lg-right text-monospace">
|
||||||
<Slot slot={Number(slot)} />
|
<Slot slot={slot} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="w-100">Parent Slot</td>
|
<td className="w-100">Parent Slot</td>
|
||||||
<td className="text-lg-right text-monospace">
|
<td className="text-lg-right text-monospace">
|
||||||
<Slot slot={confirmedBlock.data.parentSlot} link />
|
<Slot slot={block.parentSlot} link />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="w-100">Blockhash</td>
|
<td className="w-100">Blockhash</td>
|
||||||
<td className="text-lg-right text-monospace">
|
<td className="text-lg-right text-monospace">
|
||||||
<span>{confirmedBlock.data.blockhash}</span>
|
<span>{block.blockhash}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="w-100">Previous Blockhash</td>
|
<td className="w-100">Previous Blockhash</td>
|
||||||
<td className="text-lg-right text-monospace">
|
<td className="text-lg-right text-monospace">
|
||||||
<span>{confirmedBlock.data.previousBlockhash}</span>
|
<span>{block.previousBlockhash}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</TableCardBody>
|
</TableCardBody>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{confirmedBlock.data.transactions.length === 0 ? (
|
{block.transactions.length === 0 ? (
|
||||||
<ErrorCard text="This block has no transactions" />
|
<ErrorCard text="This block has no transactions" />
|
||||||
) : (
|
) : (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
|
@ -85,7 +84,7 @@ export function BlockHistoryCard({ slot }: { slot: number }) {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="list">
|
<tbody className="list">
|
||||||
{confirmedBlock.data.transactions.map((tx, i) => {
|
{block.transactions.map((tx, i) => {
|
||||||
let statusText;
|
let statusText;
|
||||||
let statusClass;
|
let statusClass;
|
||||||
let signature: React.ReactNode;
|
let signature: React.ReactNode;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { clusterPath } from "utils/url";
|
||||||
|
|
||||||
type CopyState = "copy" | "copied";
|
type CopyState = "copy" | "copied";
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -30,7 +31,7 @@ export function Slot({ slot, link }: Props) {
|
||||||
return link ? (
|
return link ? (
|
||||||
<span className="text-monospace">
|
<span className="text-monospace">
|
||||||
{copyButton}
|
{copyButton}
|
||||||
<Link className="" to={`/block/${slot}`}>
|
<Link to={clusterPath(`/block/${slot}`)}>
|
||||||
{slot.toLocaleString("en-US")}
|
{slot.toLocaleString("en-US")}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -15,8 +15,12 @@ export enum ActionType {
|
||||||
Clear,
|
Clear,
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = Cache.State<ConfirmedBlock>;
|
type Block = {
|
||||||
type Dispatch = Cache.Dispatch<ConfirmedBlock>;
|
block?: ConfirmedBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = Cache.State<Block>;
|
||||||
|
type Dispatch = Cache.Dispatch<Block>;
|
||||||
|
|
||||||
const StateContext = React.createContext<State | undefined>(undefined);
|
const StateContext = React.createContext<State | undefined>(undefined);
|
||||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
|
||||||
|
@ -25,7 +29,7 @@ type BlockProviderProps = { children: React.ReactNode };
|
||||||
|
|
||||||
export function BlockProvider({ children }: BlockProviderProps) {
|
export function BlockProvider({ children }: BlockProviderProps) {
|
||||||
const { url } = useCluster();
|
const { url } = useCluster();
|
||||||
const [state, dispatch] = Cache.useReducer<ConfirmedBlock>(url);
|
const [state, dispatch] = Cache.useReducer<Block>(url);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
dispatch({ type: ActionType.Clear, url });
|
dispatch({ type: ActionType.Clear, url });
|
||||||
|
@ -40,9 +44,7 @@ export function BlockProvider({ children }: BlockProviderProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBlock(
|
export function useBlock(key: number): Cache.CacheEntry<Block> | undefined {
|
||||||
key: number
|
|
||||||
): Cache.CacheEntry<ConfirmedBlock> | undefined {
|
|
||||||
const context = React.useContext(StateContext);
|
const context = React.useContext(StateContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
@ -66,18 +68,23 @@ export async function fetchBlock(
|
||||||
});
|
});
|
||||||
|
|
||||||
let status: FetchStatus;
|
let status: FetchStatus;
|
||||||
let data: ConfirmedBlock | undefined;
|
let data: Block | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = new Connection(url, "max");
|
const connection = new Connection(url, "max");
|
||||||
data = await connection.getConfirmedBlock(Number(key));
|
data = { block: await connection.getConfirmedBlock(Number(key)) };
|
||||||
status = FetchStatus.Fetched;
|
status = FetchStatus.Fetched;
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.log(error);
|
const error = err as Error;
|
||||||
if (cluster !== Cluster.Custom) {
|
if (error.message.includes("not found")) {
|
||||||
Sentry.captureException(error, { tags: { url } });
|
data = {} as Block;
|
||||||
|
status = FetchStatus.Fetched;
|
||||||
|
} else {
|
||||||
|
status = FetchStatus.FetchFailed;
|
||||||
|
if (cluster !== Cluster.Custom) {
|
||||||
|
Sentry.captureException(error, { tags: { url } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
status = FetchStatus.FetchFailed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -90,14 +97,12 @@ export async function fetchBlock(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFetchBlock() {
|
export function useFetchBlock() {
|
||||||
const { cluster, url } = useCluster();
|
|
||||||
const state = React.useContext(StateContext);
|
|
||||||
const dispatch = React.useContext(DispatchContext);
|
const dispatch = React.useContext(DispatchContext);
|
||||||
|
if (!dispatch) {
|
||||||
if (!state || !dispatch) {
|
|
||||||
throw new Error(`useFetchBlock must be used within a BlockProvider`);
|
throw new Error(`useFetchBlock must be used within a BlockProvider`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { cluster, url } = useCluster();
|
||||||
return React.useCallback(
|
return React.useCallback(
|
||||||
(key: number) => fetchBlock(dispatch, url, cluster, key),
|
(key: number) => fetchBlock(dispatch, url, cluster, key),
|
||||||
[dispatch, cluster, url]
|
[dispatch, cluster, url]
|
||||||
|
|
Loading…
Reference in New Issue