feat(explorer): add txs infinite scroll list (#1625)
This commit is contained in:
parent
057322c4d7
commit
ec47ee2930
@ -6,6 +6,7 @@ NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https:
|
|||||||
NX_VEGA_ENV=STAGNET3
|
NX_VEGA_ENV=STAGNET3
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||||
|
NX_BLOCK_EXPLORER=
|
||||||
|
|
||||||
# App flags
|
# App flags
|
||||||
NX_EXPLORER_ASSETS=1
|
NX_EXPLORER_ASSETS=1
|
||||||
|
@ -7,3 +7,4 @@ NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
|
|||||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||||
NX_VEGA_ENV=DEVNET
|
NX_VEGA_ENV=DEVNET
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
|
NX_BLOCK_EXPLORER=
|
||||||
|
@ -7,3 +7,4 @@ NX_VEGA_URL=https://api.vega.xyz/query
|
|||||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||||
NX_VEGA_ENV=MAINNET
|
NX_VEGA_ENV=MAINNET
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
|
NX_BLOCK_EXPLORER=
|
||||||
|
@ -7,3 +7,4 @@ NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
|
|||||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||||
NX_VEGA_ENV=STAGNET3
|
NX_VEGA_ENV=STAGNET3
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
|
NX_BLOCK_EXPLORER=
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# App configuration variables
|
# App configuration variables
|
||||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||||
NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz
|
NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz
|
||||||
|
NX_BLOCK_EXPLORER=https://n13.testnet.vega.xyz/rest
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n07.testnet.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n07.testnet.vega.xyz/websocket
|
||||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
||||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||||
|
@ -5,3 +5,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
|||||||
NX_VEGA_URL=http://localhost:3003/query
|
NX_VEGA_URL=http://localhost:3003/query
|
||||||
NX_VEGA_ENV=CUSTOM
|
NX_VEGA_ENV=CUSTOM
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
|
NX_BLOCK_EXPLORER=
|
||||||
|
@ -1,92 +1,90 @@
|
|||||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
describe('Txs infinite list item', () => {
|
describe('Txs infinite list item', () => {
|
||||||
it('should display "missing vital data" if "Type" data missing', () => {
|
it('should display "missing vital data" if "type" data missing', () => {
|
||||||
render(
|
render(
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
// @ts-ignore testing deliberate failure
|
type={undefined}
|
||||||
Type={undefined}
|
submitter="test"
|
||||||
Command={'test'}
|
hash=""
|
||||||
Sig={'test'}
|
index={0}
|
||||||
PubKey={'test'}
|
block="1"
|
||||||
Nonce={1}
|
|
||||||
TxHash={'test'}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display "missing vital data" if "Command" data missing', () => {
|
it('should display "missing vital data" if "hash" data missing', () => {
|
||||||
render(
|
render(
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
Type={'test'}
|
type="test"
|
||||||
// @ts-ignore testing deliberate failure
|
submitter="test"
|
||||||
Command={undefined}
|
hash={undefined}
|
||||||
Sig={'test'}
|
index={0}
|
||||||
PubKey={'test'}
|
block="1"
|
||||||
Nonce={1}
|
|
||||||
TxHash={'test'}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display "missing vital data" if "Pubkey" data missing', () => {
|
it('should display "missing vital data" if "submitter" data missing', () => {
|
||||||
render(
|
render(
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
Type={'test'}
|
type="test"
|
||||||
Command={'test'}
|
submitter={undefined}
|
||||||
Sig={'test'}
|
hash="test"
|
||||||
// @ts-ignore testing deliberate failure
|
index={0}
|
||||||
PubKey={undefined}
|
block="1"
|
||||||
Nonce={1}
|
|
||||||
TxHash={'test'}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display "missing vital data" if "TxHash" data missing', () => {
|
it('should display "missing vital data" if "block" data missing', () => {
|
||||||
render(
|
render(
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
Type={'test'}
|
type="test"
|
||||||
Command={'test'}
|
submitter="test"
|
||||||
Sig={'test'}
|
hash="test"
|
||||||
PubKey={'test'}
|
index={0}
|
||||||
Nonce={1}
|
block={undefined}
|
||||||
// @ts-ignore testing deliberate failure
|
/>
|
||||||
TxHash={undefined}
|
);
|
||||||
|
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display "missing vital data" if "index" data missing', () => {
|
||||||
|
render(
|
||||||
|
<TxsInfiniteListItem
|
||||||
|
type="test"
|
||||||
|
submitter="test"
|
||||||
|
hash="test"
|
||||||
|
index={undefined}
|
||||||
|
block="1"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders data correctly', () => {
|
it('renders data correctly', () => {
|
||||||
const testCommandData = JSON.stringify({
|
|
||||||
test: 'test of command data',
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
Type={'testType'}
|
type="testType"
|
||||||
Command={testCommandData}
|
submitter="testPubKey"
|
||||||
Sig={'testSig'}
|
hash="testTxHash"
|
||||||
PubKey={'testPubKey'}
|
index={1}
|
||||||
Nonce={1}
|
block="1"
|
||||||
TxHash={'testTxHash'}
|
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
|
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
|
||||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||||
expect(screen.getByTestId('type')).toHaveTextContent('testType');
|
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||||
const button = screen.getByTestId('command-details');
|
expect(screen.getByTestId('tx-block')).toHaveTextContent(
|
||||||
act(() => {
|
'Block 1 (index 1)'
|
||||||
fireEvent.click(button);
|
);
|
||||||
});
|
|
||||||
expect(screen.getByText('"test of command data"')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,69 +1,60 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
Icon,
|
|
||||||
Intent,
|
|
||||||
SyntaxHighlighter,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { TruncatedLink } from '../truncate/truncated-link';
|
import { TruncatedLink } from '../truncate/truncated-link';
|
||||||
import { Routes } from '../../routes/route-names';
|
import { Routes } from '../../routes/route-names';
|
||||||
import { TxOrderType } from './tx-order-type';
|
import { TxOrderType } from './tx-order-type';
|
||||||
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
|
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
|
||||||
|
import { toHex } from '../search/detect-search';
|
||||||
|
|
||||||
const TRUNCATE_LENGTH = 14;
|
const TRUNCATE_LENGTH = 14;
|
||||||
|
|
||||||
export const TxsInfiniteListItem = ({
|
export const TxsInfiniteListItem = ({
|
||||||
TxHash,
|
hash,
|
||||||
PubKey,
|
submitter,
|
||||||
Type,
|
type,
|
||||||
Command,
|
block,
|
||||||
}: ChainExplorerTxResponse) => {
|
index,
|
||||||
const [open, setOpen] = useState(false);
|
}: Partial<BlockExplorerTransaction>) => {
|
||||||
|
if (
|
||||||
if (!TxHash || !PubKey || !Type || !Command) {
|
!hash ||
|
||||||
|
!submitter ||
|
||||||
|
!type ||
|
||||||
|
block === undefined ||
|
||||||
|
index === undefined
|
||||||
|
) {
|
||||||
return <div>Missing vital data</div>;
|
return <div>Missing vital data</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid="transaction-row"
|
data-testid="transaction-row"
|
||||||
className="grid grid-cols-[repeat(2,_1fr)_240px] gap-12 w-full border-t border-neutral-600 dark:border-neutral-800 py-8 txs-infinite-list-item"
|
className="grid grid-flow-col auto-cols-auto border-t border-neutral-600 dark:border-neutral-800 py-2 txs-infinite-list-item"
|
||||||
>
|
>
|
||||||
<div className="whitespace-nowrap overflow-scroll" data-testid="tx-hash">
|
<div className="whitespace-nowrap" data-testid="tx-type">
|
||||||
|
<TxOrderType orderType={type} />
|
||||||
|
</div>
|
||||||
|
<div className="whitespace-nowrap" data-testid="pub-key">
|
||||||
<TruncatedLink
|
<TruncatedLink
|
||||||
to={`/${Routes.TX}/${TxHash}`}
|
to={`/${Routes.PARTIES}/${submitter}`}
|
||||||
text={TxHash}
|
text={submitter}
|
||||||
startChars={TRUNCATE_LENGTH}
|
startChars={TRUNCATE_LENGTH}
|
||||||
endChars={TRUNCATE_LENGTH}
|
endChars={TRUNCATE_LENGTH}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="whitespace-nowrap overflow-scroll" data-testid="pub-key">
|
<div className="whitespace-nowrap" data-testid="tx-hash">
|
||||||
<TruncatedLink
|
<TruncatedLink
|
||||||
to={`/${Routes.PARTIES}/${PubKey}`}
|
to={`/${Routes.TX}/${toHex(hash)}`}
|
||||||
text={PubKey}
|
text={hash}
|
||||||
startChars={TRUNCATE_LENGTH}
|
startChars={TRUNCATE_LENGTH}
|
||||||
endChars={TRUNCATE_LENGTH}
|
endChars={TRUNCATE_LENGTH}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="whitespace-nowrap" data-testid="tx-block">
|
||||||
className="flex justify-between whitespace-nowrap overflow-scroll"
|
<TruncatedLink
|
||||||
data-testid="type"
|
to={`/${Routes.BLOCKS}/${block}`}
|
||||||
>
|
text={`Block ${block} (index ${index})`}
|
||||||
<TxOrderType orderType={Type} />
|
startChars={TRUNCATE_LENGTH}
|
||||||
<button
|
endChars={TRUNCATE_LENGTH}
|
||||||
title="More details"
|
/>
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
data-testid="command-details"
|
|
||||||
>
|
|
||||||
<Icon name="search-template" />
|
|
||||||
</button>
|
|
||||||
<Dialog
|
|
||||||
open={open}
|
|
||||||
onChange={(isOpen) => setOpen(false)}
|
|
||||||
intent={Intent.None}
|
|
||||||
>
|
|
||||||
<SyntaxHighlighter data={JSON.parse(Command)} />
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,34 @@
|
|||||||
import { TxsInfiniteList } from './txs-infinite-list';
|
import { TxsInfiniteList } from './txs-infinite-list';
|
||||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
|
||||||
|
|
||||||
const generateTxs = (number: number) => {
|
const generateTxs = (number: number): BlockExplorerTransaction[] => {
|
||||||
return Array.from(Array(number)).map((_) => ({
|
return Array.from(Array(number)).map((_) => ({
|
||||||
Type: 'ChainEvent',
|
block: '87901',
|
||||||
Command:
|
index: 2,
|
||||||
'{"txId":"0xc8941ac4ea989988cb8f72e8fdab2e2009376fd17619491439d36b519d27bc93","nonce":"1494","stakingEvent":{"index":"263","block":"14805346","stakeDeposited":{"ethereumAddress":"0x2e5fe63e5d49c26998cf4bfa9b64de1cf9ae7ef2","vegaPublicKey":"657c2a8a5867c43c831e24820b7544e2fdcc1cf610cfe0ece940fe78137400fd","amount":"38471116086510047870875","blockTime":"1652968806"}}}',
|
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
|
||||||
Sig: 'fe7624ab742c492cf1e667e79de4777992aca8e093c8707e1f22685c3125c6082cd21b85cd966a61ad4ca0cca2f8bed3082565caa5915bc3b2f78c1ae35cac0b',
|
submitter:
|
||||||
PubKey:
|
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||||
'0x7d69327393cdfaaae50e5e215feca65273eafabfb38f32b8124e66298af346d5',
|
type: 'Submit Order',
|
||||||
Nonce: 18296387398179254000,
|
code: 0,
|
||||||
TxHash:
|
cursor: '87901.2',
|
||||||
'0x9C753FA6325F7A40D9C4FA5C25E24476C54613E12B1FA2DD841E3BB00D088B77',
|
command: {
|
||||||
|
nonce: '4214037379192575529',
|
||||||
|
blockHeight: '87898',
|
||||||
|
orderSubmission: {
|
||||||
|
marketId:
|
||||||
|
'b4d0a070f5cc73a7d53b23d6f63f8cb52e937ed65d2469a3af4cc1e80e155fcf',
|
||||||
|
price: '14525946',
|
||||||
|
size: '54',
|
||||||
|
side: 'SIDE_SELL',
|
||||||
|
timeInForce: 'TIME_IN_FORCE_GTT',
|
||||||
|
expiresAt: '1664966445481288736',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
reference: 'traderbot',
|
||||||
|
peggedOrder: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,7 +57,9 @@ describe('Txs infinite list', () => {
|
|||||||
error={Error('test error!')}
|
error={Error('test error!')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('Error: test error!')).toBeInTheDocument();
|
expect(
|
||||||
|
screen.getByText('Cannot fetch transaction: Error: test error!')
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('item renders data of n length into list of n length', () => {
|
it('item renders data of n length into list of n length', () => {
|
||||||
|
@ -3,19 +3,19 @@ import { FixedSizeList as List } from 'react-window';
|
|||||||
import InfiniteLoader from 'react-window-infinite-loader';
|
import InfiniteLoader from 'react-window-infinite-loader';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||||
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
|
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
|
||||||
|
|
||||||
interface TxsInfiniteListProps {
|
interface TxsInfiniteListProps {
|
||||||
hasMoreTxs: boolean;
|
hasMoreTxs: boolean;
|
||||||
areTxsLoading: boolean | undefined;
|
areTxsLoading: boolean | undefined;
|
||||||
txs: ChainExplorerTxResponse[] | undefined;
|
txs: BlockExplorerTransaction[] | undefined;
|
||||||
loadMoreTxs: () => void;
|
loadMoreTxs: () => void;
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps {
|
||||||
index: ChainExplorerTxResponse;
|
index: BlockExplorerTransaction;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
@ -27,19 +27,19 @@ const NOOP = () => {};
|
|||||||
const Item = ({ index, style, isLoading, error }: ItemProps) => {
|
const Item = ({ index, style, isLoading, error }: ItemProps) => {
|
||||||
let content;
|
let content;
|
||||||
if (error) {
|
if (error) {
|
||||||
content = t(`${error}`);
|
content = t(`Cannot fetch transaction: ${error}`);
|
||||||
} else if (isLoading) {
|
} else if (isLoading) {
|
||||||
content = t('Loading...');
|
content = t('Loading...');
|
||||||
} else {
|
} else {
|
||||||
const { TxHash, PubKey, Type, Command, Sig, Nonce } = index;
|
const { hash, submitter, type, command, block, index: blockIndex } = index;
|
||||||
content = (
|
content = (
|
||||||
<TxsInfiniteListItem
|
<TxsInfiniteListItem
|
||||||
Type={Type}
|
type={type}
|
||||||
Command={Command}
|
command={command}
|
||||||
Sig={Sig}
|
submitter={submitter}
|
||||||
PubKey={PubKey}
|
hash={hash}
|
||||||
Nonce={Nonce}
|
block={block}
|
||||||
TxHash={TxHash}
|
index={blockIndex}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -71,10 +71,11 @@ export const TxsInfiniteList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} data-testid="transactions-list">
|
<div className={className} data-testid="transactions-list">
|
||||||
<div className="grid grid-cols-[repeat(2,_1fr)_240px] gap-12 w-full mb-8">
|
<div className="grid grid-flow-col auto-cols-auto w-full mb-8">
|
||||||
<div className="text-lg font-bold">Txn hash</div>
|
|
||||||
<div className="text-lg font-bold">Party</div>
|
|
||||||
<div className="text-lg font-bold pl-2">Type</div>
|
<div className="text-lg font-bold pl-2">Type</div>
|
||||||
|
<div className="text-lg font-bold">Submitted By</div>
|
||||||
|
<div className="text-lg font-bold">Transaction ID</div>
|
||||||
|
<div className="text-lg font-bold">Block</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-testid="infinite-scroll-wrapper">
|
<div data-testid="infinite-scroll-wrapper">
|
||||||
<InfiniteLoader
|
<InfiniteLoader
|
||||||
|
@ -13,6 +13,7 @@ export const ENV = {
|
|||||||
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
|
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
|
||||||
dataSources: {
|
dataSources: {
|
||||||
chainExplorerUrl: windowOrDefault('NX_CHAIN_EXPLORER_URL'),
|
chainExplorerUrl: windowOrDefault('NX_CHAIN_EXPLORER_URL'),
|
||||||
|
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
|
||||||
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
|
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
|
||||||
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
|
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@ import Genesis from './genesis';
|
|||||||
import { Block } from './blocks/id';
|
import { Block } from './blocks/id';
|
||||||
import { Blocks } from './blocks/home';
|
import { Blocks } from './blocks/home';
|
||||||
import { Tx } from './txs/id';
|
import { Tx } from './txs/id';
|
||||||
import { TxsHome, TxsHomeFallback } from './txs/home';
|
import { TxsList } from './txs/home';
|
||||||
import { PendingTxs } from './pending';
|
import { PendingTxs } from './pending';
|
||||||
import flags from '../config/flags';
|
import flags from '../config/flags';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
@ -141,7 +141,7 @@ const routerConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element: flags.txsList ? <TxsHome /> : <TxsHomeFallback />,
|
element: <TxsList />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -1,86 +1,73 @@
|
|||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import { useCallback, useState, useMemo } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
||||||
import { RouteTitle } from '../../../components/route-title';
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
import { BlocksRefetch } from '../../../components/blocks';
|
import { BlocksRefetch } from '../../../components/blocks';
|
||||||
import { JumpToBlock } from '../../../components/jump-to-block';
|
|
||||||
import { TxsInfiniteList } from '../../../components/txs';
|
import { TxsInfiniteList } from '../../../components/txs';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import type {
|
||||||
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
|
BlockExplorerTransaction,
|
||||||
import type { TendermintBlockchainResponse } from '../../blocks/tendermint-blockchain-response';
|
BlockExplorerTransactions,
|
||||||
|
} from '../../../routes/types/block-explorer-response';
|
||||||
interface TxsProps {
|
|
||||||
latestBlockHeight: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TxsStateProps {
|
interface TxsStateProps {
|
||||||
txsData: ChainExplorerTxResponse[];
|
txsData: BlockExplorerTransaction[];
|
||||||
hasMoreTxs: boolean;
|
hasMoreTxs: boolean;
|
||||||
nextPage: number;
|
lastCursor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Txs = ({ latestBlockHeight }: TxsProps) => {
|
const BE_TXS_PER_REQUEST = 100;
|
||||||
const [{ txsData, hasMoreTxs, nextPage }, setTxsState] =
|
|
||||||
|
export const TxsList = () => {
|
||||||
|
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
|
||||||
useState<TxsStateProps>({
|
useState<TxsStateProps>({
|
||||||
txsData: [],
|
txsData: [],
|
||||||
hasMoreTxs: true,
|
hasMoreTxs: true,
|
||||||
nextPage: 1,
|
lastCursor: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const reusedBodyParams = useMemo(
|
|
||||||
() => ({
|
|
||||||
node_url: DATA_SOURCES.tendermintUrl,
|
|
||||||
transaction_height: parseInt(latestBlockHeight),
|
|
||||||
page_size: 30,
|
|
||||||
}),
|
|
||||||
[latestBlockHeight]
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: { error, loading },
|
state: { data, error, loading },
|
||||||
refetch,
|
refetch,
|
||||||
} = useFetch(
|
} = useFetch<BlockExplorerTransactions>(
|
||||||
DATA_SOURCES.chainExplorerUrl,
|
`${DATA_SOURCES.blockExplorerUrl}/transactions?` +
|
||||||
{
|
new URLSearchParams({
|
||||||
method: 'POST',
|
limit: BE_TXS_PER_REQUEST.toString(10),
|
||||||
body: JSON.stringify(reusedBodyParams),
|
}),
|
||||||
},
|
{},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadTxs = useCallback(async () => {
|
useEffect(() => {
|
||||||
const data = await refetch(
|
if (data?.transactions?.length) {
|
||||||
undefined,
|
|
||||||
JSON.stringify({
|
|
||||||
...reusedBodyParams,
|
|
||||||
page_number: nextPage,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
setTxsState((prev) => ({
|
setTxsState((prev) => ({
|
||||||
...prev,
|
txsData: [...prev.txsData, ...data.transactions],
|
||||||
nextPage: prev.nextPage + 1,
|
|
||||||
hasMoreTxs: true,
|
hasMoreTxs: true,
|
||||||
txsData: [...prev.txsData, ...(data as ChainExplorerTxResponse[])],
|
lastCursor:
|
||||||
|
data.transactions[data.transactions.length - 1].cursor || '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [nextPage, refetch, reusedBodyParams]);
|
}, [data?.transactions]);
|
||||||
|
|
||||||
|
const loadTxs = useCallback(() => {
|
||||||
|
return refetch({
|
||||||
|
limit: BE_TXS_PER_REQUEST,
|
||||||
|
before: lastCursor,
|
||||||
|
});
|
||||||
|
}, [lastCursor, refetch]);
|
||||||
|
|
||||||
|
const refreshTxs = useCallback(async () => {
|
||||||
|
setTxsState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
lastCursor: '',
|
||||||
|
hasMoreTxs: true,
|
||||||
|
txsData: [],
|
||||||
|
}));
|
||||||
|
}, [setTxsState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<RouteTitle>{t('Transactions')}</RouteTitle>
|
<RouteTitle>{t('Transactions')}</RouteTitle>
|
||||||
<BlocksRefetch
|
<BlocksRefetch refetch={refreshTxs} />
|
||||||
refetch={() =>
|
|
||||||
refetch(
|
|
||||||
undefined,
|
|
||||||
JSON.stringify({
|
|
||||||
...reusedBodyParams,
|
|
||||||
page_number: 1,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TxsInfiniteList
|
<TxsInfiniteList
|
||||||
hasMoreTxs={hasMoreTxs}
|
hasMoreTxs={hasMoreTxs}
|
||||||
areTxsLoading={loading}
|
areTxsLoading={loading}
|
||||||
@ -89,42 +76,6 @@ const Txs = ({ latestBlockHeight }: TxsProps) => {
|
|||||||
error={error}
|
error={error}
|
||||||
className="mb-28"
|
className="mb-28"
|
||||||
/>
|
/>
|
||||||
<JumpToBlock />
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TxsHome = () => {
|
|
||||||
const {
|
|
||||||
state: { data, error, loading },
|
|
||||||
} = useFetch<TendermintBlockchainResponse>(
|
|
||||||
`${DATA_SOURCES.tendermintUrl}/blockchain`
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AsyncRenderer
|
|
||||||
loading={!!loading}
|
|
||||||
loadingMessage={t('Getting latest block height...')}
|
|
||||||
error={error}
|
|
||||||
data={data}
|
|
||||||
noDataMessage={t('Could not get latest block height')}
|
|
||||||
render={(data) => (
|
|
||||||
<Txs
|
|
||||||
latestBlockHeight={
|
|
||||||
data?.result?.block_metas?.[0]?.header?.height || ''
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TxsHomeFallback = () => (
|
|
||||||
<>
|
|
||||||
<RouteTitle>{t('Transactions')}</RouteTitle>
|
|
||||||
<div>
|
|
||||||
The transactions list is currently disabled. Please use the search bar to
|
|
||||||
discover transaction data
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
14
apps/explorer/src/app/routes/types/block-explorer-response.d.ts
vendored
Normal file
14
apps/explorer/src/app/routes/types/block-explorer-response.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface BlockExplorerTransaction {
|
||||||
|
block: string;
|
||||||
|
index: number;
|
||||||
|
hash: string;
|
||||||
|
submitter: string;
|
||||||
|
type: string;
|
||||||
|
code: number;
|
||||||
|
cursor: string;
|
||||||
|
command: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockExplorerTransactions {
|
||||||
|
transactions: BlockExplorerTransaction[];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user