Skip to content

Commit

Permalink
[explorer] - checkpoint details page (MystenLabs#8631)
Browse files Browse the repository at this point in the history
## Description 

<img width="1438" alt="image"
src="https://user-images.githubusercontent.com/122397493/221307336-4055fea3-0a46-4141-8de2-48e295ba0cd1.png">

Adds detail page for viewing details for a specific checkpoint. 
   - adds route `/checkpoint/:digest`
- includes feature flag `explorer-epochs-checkpoints`. added this before
we paused mainnet release, but left it in since most of the other
epochs/checkpoints pages contain mocks
- another callout is right now we're doing a batch request to display
transactions within a checkpoint, ideally we could query for
transactions by a specific checkpoint
   - adds ability to search by checkpoint digest
- created [APPS-543](https://mysten.atlassian.net/browse/APPS-543) as a
follow-up to clean-up / enable re-use of the transactions table / logic

## Test Plan 

manual testing

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.
  • Loading branch information
mamos-mysten authored Mar 2, 2023
1 parent 24f6675 commit 623dcbe
Show file tree
Hide file tree
Showing 14 changed files with 751 additions and 1,966 deletions.
4 changes: 2 additions & 2 deletions apps/explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"@floating-ui/react": "^0.18.0",
"@fontsource/inter": "^4.5.14",
"@fontsource/red-hat-mono": "^4.5.11",
"@growthbook/growthbook": "^0.20.1",
"@growthbook/growthbook-react": "^0.10.1",
"@growthbook/growthbook": "^0.21.2",
"@growthbook/growthbook-react": "^0.11.2",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@mysten/core": "workspace:*",
Expand Down
9 changes: 7 additions & 2 deletions apps/explorer/src/components/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ function Search() {
const navigate = useNavigateWithQuery();
const handleSelectResult = useCallback(
(result: SearchResult) => {
navigate(`/${result.type}/${encodeURIComponent(result.id)}`, {});
setQuery('');
if (result) {
navigate(
`/${result?.type}/${encodeURIComponent(result?.id)}`,
{}
);
setQuery('');
}
},
[navigate]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ export function LatestTxCard({
const recentTx = useMemo(
() =>
transactionQuery.data
? genTableDataFromTxData(transactionQuery.data, truncateLength)
? genTableDataFromTxData(transactionQuery.data)
: null,
[transactionQuery.data, truncateLength]
[transactionQuery.data]
);

const stats = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ export function TxAddresses({ content }: { content: LinkObj[] }) {
}

// Generate table data from the transaction data
export const genTableDataFromTxData = (
results: TxnData[],
truncateLength: number
) => ({
export const genTableDataFromTxData = (results: TxnData[]) => ({
data: results.map((txn) => ({
date: <TxTimeType timestamp={txn.timestamp_ms} />,
transactionId: (
Expand Down
3 changes: 1 addition & 2 deletions apps/explorer/src/components/transaction-card/TxForID.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { useRpc } from '~/hooks/useRpc';
import { Banner } from '~/ui/Banner';
import { TableCard } from '~/ui/TableCard';

const TRUNCATE_LENGTH = 14;
const ITEMS_PER_PAGE = 20;

const DATATYPE_DEFAULT = {
Expand All @@ -40,7 +39,7 @@ const viewFn = (results: any) => <TxForIDView showData={results} />;
function TxForIDView({ showData }: { showData: TxnData[] | undefined }) {
if (!showData || showData.length === 0) return null;

const tableData = genTableDataFromTxData(showData, TRUNCATE_LENGTH);
const tableData = genTableDataFromTxData(showData);

return (
<div data-testid="tx">
Expand Down
45 changes: 39 additions & 6 deletions apps/explorer/src/hooks/useSearch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useFeature } from '@growthbook/growthbook-react';
import {
isValidTransactionDigest,
isValidSuiAddress,
Expand All @@ -15,6 +16,7 @@ import { useQuery } from '@tanstack/react-query';

import { useRpc } from '~/hooks/useRpc';
import { isGenesisLibAddress } from '~/utils/api/searchUtil';
import { GROWTHBOOK_FEATURES } from '~/utils/growthbook';

type Result = {
label: string;
Expand Down Expand Up @@ -57,6 +59,25 @@ const getResultsForObject = async (rpc: JsonRpcProvider, query: string) => {
],
};
}

return null;
};

const getResultsForCheckpoint = async (rpc: JsonRpcProvider, query: string) => {
const { digest } = await rpc.getCheckpoint(query);
if (digest) {
return {
label: 'checkpoint',
results: [
{
id: digest,
label: digest,
type: 'checkpoint',
},
],
};
}

return null;
};

Expand All @@ -69,6 +90,7 @@ const getResultsForAddress = async (rpc: JsonRpcProvider, query: string) => {
rpc.getTransactions({ FromAddress: normalized }, null, 1),
rpc.getTransactions({ ToAddress: normalized }, null, 1),
]);

if (from.data?.length || to.data?.length) {
return {
label: 'address',
Expand All @@ -81,22 +103,33 @@ const getResultsForAddress = async (rpc: JsonRpcProvider, query: string) => {
],
};
}

return null;
};

export function useSearch(query: string) {
const rpc = useRpc();
const checkpointsEnabled = useFeature(
GROWTHBOOK_FEATURES.EPOCHS_CHECKPOINTS
).on;

return useQuery(
['search', query],
async () => {
const results = await Promise.all([
getResultsForTransaction(rpc, query),
getResultsForAddress(rpc, query),
getResultsForObject(rpc, query),
]);
const results = (
await Promise.allSettled([
getResultsForTransaction(rpc, query),
...(checkpointsEnabled
? [getResultsForCheckpoint(rpc, query)]
: []),
getResultsForAddress(rpc, query),
getResultsForObject(rpc, query),
])
).filter(
(r) => r.status === 'fulfilled' && r.value
) as PromiseFulfilledResult<Result>[];

return results.filter(Boolean) as Result[];
return results.map(({ value }) => value);
},
{
enabled: !!query,
Expand Down
163 changes: 163 additions & 0 deletions apps/explorer/src/pages/checkpoints/CheckpointDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useFeature, useGrowthBook } from '@growthbook/growthbook-react';
import { useQuery } from '@tanstack/react-query';
import { Navigate, useParams } from 'react-router-dom';

import { CheckpointTransactions } from './Transactions';

import { useRpc } from '~/hooks/useRpc';
import { Banner } from '~/ui/Banner';
import { DescriptionList, DescriptionItem } from '~/ui/DescriptionList';
import { LoadingSpinner } from '~/ui/LoadingSpinner';
import { PageHeader } from '~/ui/PageHeader';
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '~/ui/Tabs';
import { Text } from '~/ui/Text';
import { GROWTHBOOK_FEATURES } from '~/utils/growthbook';
import { convertNumberToDate } from '~/utils/timeUtils';

function CheckpointDetail() {
const { digest } = useParams<{ digest: string }>();
const rpc = useRpc();

const checkpointQuery = useQuery(['checkpoints', digest], () =>
rpc.getCheckpoint(digest!)
);

// todo: add user_signatures to combined `getCheckpoint` endpoint
const contentsQuery = useQuery(
['checkpoints', digest, 'contents'],
() => rpc.getCheckpointContents(checkpoint.sequenceNumber),
{ enabled: !!checkpointQuery.data }
);

if (checkpointQuery.isError)
return (
<Banner variant="error" fullWidth>
There was an issue retrieving data for checkpoint: {digest}
</Banner>
);

if (checkpointQuery.isLoading) return <LoadingSpinner />;

const {
data: { epochRollingGasCostSummary, ...checkpoint },
} = checkpointQuery;

return (
<div className="flex flex-col space-y-12">
<PageHeader title={checkpoint.digest} type="Checkpoint" />
<div className="space-y-8">
<TabGroup as="div" size="lg">
<TabList>
<Tab>Details</Tab>
<Tab>Signatures</Tab>
</TabList>
<TabPanels>
<TabPanel>
<DescriptionList>
<DescriptionItem title="Checkpoint Sequence No.">
<Text
variant="p1/medium"
color="steel-darker"
>
{checkpoint.sequenceNumber}
</Text>
</DescriptionItem>
<DescriptionItem title="Epoch">
<Text
variant="p1/medium"
color="steel-darker"
>
{checkpoint.epoch}
</Text>
</DescriptionItem>
<DescriptionItem title="Checkpoint Timestamp">
<Text
variant="p1/medium"
color="steel-darker"
>
{checkpoint.timestampMs
? convertNumberToDate(
checkpoint.timestampMs
)
: '--'}
</Text>
</DescriptionItem>
</DescriptionList>
</TabPanel>
<TabPanel>
<DescriptionList>
{contentsQuery.data?.user_signatures.map(
([signature]) => (
<DescriptionItem
key={signature}
title="Signature"
>
<Text
variant="p1/medium"
color="steel-darker"
>
{signature}
</Text>
</DescriptionItem>
)
)}
</DescriptionList>
</TabPanel>
</TabPanels>
</TabGroup>
<TabGroup as="div" size="lg">
<TabList>
<Tab>Gas & Storage Fee</Tab>
</TabList>
<TabPanels>
<DescriptionList>
<DescriptionItem title="Computation Fee">
<Text variant="p1/medium" color="steel-darker">
{
epochRollingGasCostSummary.computation_cost
}
</Text>
</DescriptionItem>
<DescriptionItem title="Storage Fee">
<Text variant="p1/medium" color="steel-darker">
{epochRollingGasCostSummary.storage_cost}
</Text>
</DescriptionItem>
<DescriptionItem title="Storage Rebate">
<Text variant="p1/medium" color="steel-darker">
{epochRollingGasCostSummary.storage_rebate}
</Text>
</DescriptionItem>
</DescriptionList>
</TabPanels>
</TabGroup>

<TabGroup as="div" size="lg">
<TabList>
<Tab>Checkpoint Transactions</Tab>
</TabList>
<TabPanels>
<div className="mt-4">
<CheckpointTransactions
digest={checkpoint.digest}
transactions={checkpoint.transactions || []}
/>
</div>
</TabPanels>
</TabGroup>
</div>
</div>
);
}

export default function CheckpointDetailFeatureFlagged() {
const gb = useGrowthBook();
const enabled = useFeature(GROWTHBOOK_FEATURES.EPOCHS_CHECKPOINTS).on;
if (gb?.ready) {
return enabled ? <CheckpointDetail /> : <Navigate to="/" />;
}
return <LoadingSpinner />;
}
37 changes: 37 additions & 0 deletions apps/explorer/src/pages/checkpoints/Transactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useQuery } from '@tanstack/react-query';

import {
genTableDataFromTxData,
getDataOnTxDigests,
type TxnData,
} from '~/components/transaction-card/TxCardUtils';
import { useRpc } from '~/hooks/useRpc';
import { TableCard } from '~/ui/TableCard';

export function CheckpointTransactions({
digest,
transactions,
}: {
digest: string;
transactions: string[];
}) {
const rpc = useRpc();
const { data: txData, isLoading } = useQuery(
['checkpoint-transactions', digest],
async () => {
// todo: replace this with `sui_getTransactions` call when we are
// able to query by checkpoint digest
const txData = await getDataOnTxDigests(rpc, transactions!);
return genTableDataFromTxData(txData as TxnData[]);
},
{ enabled: !!transactions.length }
);
if (isLoading) return null;

return txData ? (
<TableCard data={txData?.data} columns={txData?.columns} />
) : null;
}
2 changes: 2 additions & 0 deletions apps/explorer/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from 'react-router-dom';

import AddressResult from './address-result/AddressResult';
import CheckpointDetail from './checkpoints/CheckpointDetail';
import Home from './home/Home';
import { ObjectResult } from './object-result/ObjectResult';
import SearchError from './searcherror/SearchError';
Expand All @@ -36,6 +37,7 @@ export const router = sentryCreateBrowserRouter([
{ path: '/', element: <Home /> },
{ path: 'transactions', element: <Transactions /> },
{ path: 'object/:id', element: <ObjectResult /> },
{ path: 'checkpoint/:digest', element: <CheckpointDetail /> },
{ path: 'transaction/:id', element: <TransactionResult /> },
{ path: 'address/:id', element: <AddressResult /> },
{ path: 'validators', element: <ValidatorPageResult /> },
Expand Down
Loading

0 comments on commit 623dcbe

Please sign in to comment.