Feat/1682 fix api endpoints (#1690)

* chore(explorer): replace tendermint api with block explorer api for transaction related searches

* feat(explorer): replace detect search type to be by process of elimination as temp measure

* fix(explorer): fix broken tests
This commit is contained in:
Elmar 2022-10-11 14:17:10 +01:00 committed by GitHub
parent 0b8c9c836c
commit a7d100bce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 199 additions and 234 deletions

View File

@ -89,8 +89,8 @@ describe('Detect Search', () => {
expect(actual).toStrictEqual(expected); expect(actual).toStrictEqual(expected);
}); });
it("detectTypeByFetching should call fetch with hex query it's a transaction", async () => { it("detectTypeByFetching should call fetch with non-hex query it's a transaction", async () => {
const query = 'abc'; const query = '0xabc';
const type = SearchTypes.Transaction; const type = SearchTypes.Transaction;
// @ts-ignore issue related to polyfill // @ts-ignore issue related to polyfill
fetch.mockImplementation( fetch.mockImplementation(
@ -99,45 +99,21 @@ describe('Detect Search', () => {
ok: true, ok: true,
json: () => json: () =>
Promise.resolve({ Promise.resolve({
result: { transaction: {
tx: query, hash: query,
}, },
}), }),
}) })
) )
); );
const result = await detectTypeByFetching(query, type); const result = await detectTypeByFetching(query);
expect(fetch).toHaveBeenCalledWith( expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.tendermintUrl}/tx?hash=0x${query}` `${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(query)}`
); );
expect(result).toBe(type); expect(result).toBe(type);
}); });
it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => { it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => {
const query = 'abc';
const type = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
result: {
txs: [query],
},
}),
})
)
);
const result = await detectTypeByFetching(query, type);
expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
);
expect(result).toBe(type);
});
it('detectTypeByFetching should return undefined if no matches', async () => {
const query = 'abc'; const query = 'abc';
const type = SearchTypes.Party; const type = SearchTypes.Party;
// @ts-ignore issue related to polyfill // @ts-ignore issue related to polyfill
@ -148,11 +124,8 @@ describe('Detect Search', () => {
}) })
) )
); );
const result = await detectTypeByFetching(query, type); const result = await detectTypeByFetching(query);
expect(fetch).toHaveBeenCalledWith( expect(result).toBe(type);
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${query}'"`
);
expect(result).toBe(undefined);
}); });
it('getSearchType should return party from fetch response', async () => { it('getSearchType should return party from fetch response', async () => {
@ -163,13 +136,7 @@ describe('Detect Search', () => {
fetch.mockImplementation( fetch.mockImplementation(
jest.fn(() => jest.fn(() =>
Promise.resolve({ Promise.resolve({
ok: true, ok: false,
json: () =>
Promise.resolve({
result: {
txs: [query],
},
}),
}) })
) )
); );
@ -177,7 +144,7 @@ describe('Detect Search', () => {
expect(result).toBe(expected); expect(result).toBe(expected);
}); });
it('getSearchType should return party from transaction response', async () => { it('getSearchType should return transaction from fetch response', async () => {
const query = const query =
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F'; '4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = SearchTypes.Transaction; const expected = SearchTypes.Transaction;
@ -188,8 +155,8 @@ describe('Detect Search', () => {
ok: true, ok: true,
json: () => json: () =>
Promise.resolve({ Promise.resolve({
result: { transaction: {
tx: query, hash: query,
}, },
}), }),
}) })
@ -200,17 +167,8 @@ describe('Detect Search', () => {
}); });
it('getSearchType should return undefined from transaction response', async () => { it('getSearchType should return undefined from transaction response', async () => {
const query = const query = 'u';
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = undefined; const expected = undefined;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: false,
})
)
);
const result = await getSearchType(query); const result = await getSearchType(query);
expect(result).toBe(expected); expect(result).toBe(expected);
}); });

View File

@ -1,4 +1,5 @@
import { DATA_SOURCES } from '../../config'; import { DATA_SOURCES } from '../../config';
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
export enum SearchTypes { export enum SearchTypes {
Transaction = 'transaction', Transaction = 'transaction',
@ -42,46 +43,88 @@ export const detectTypeFromQuery = (
}; };
export const detectTypeByFetching = async ( export const detectTypeByFetching = async (
query: string, query: string
type: SearchTypes
): Promise<SearchTypes | undefined> => { ): Promise<SearchTypes | undefined> => {
const TYPES = [SearchTypes.Party, SearchTypes.Transaction]; const hash = toNonHex(query);
const request = await fetch(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
);
if (!TYPES.includes(type)) { if (request?.ok) {
throw new Error('Search type provided not recognised'); const body: BlockExplorerTransaction = await request.json();
}
if (type === SearchTypes.Transaction) { if (body?.transaction) {
const hash = toHex(query); return SearchTypes.Transaction;
const request = await fetch(
`${DATA_SOURCES.tendermintUrl}/tx?hash=${hash}`
);
if (request?.ok) {
const body = await request.json();
if (body?.result?.tx) {
return SearchTypes.Transaction;
}
}
} else if (type === SearchTypes.Party) {
const party = toNonHex(query);
const request = await fetch(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${party}'"`
);
if (request.ok) {
const body = await request.json();
if (body?.result?.txs?.length) {
return SearchTypes.Party;
}
} }
} }
return undefined; return SearchTypes.Party;
}; };
// Code commented out because the current solution to detect a hex is temporary (by process of elimination)
// export const detectTypeByFetching = async (
// query: string,
// type: SearchTypes
// ): Promise<SearchTypes | undefined> => {
// const TYPES = [SearchTypes.Party, SearchTypes.Transaction];
//
// if (!TYPES.includes(type)) {
// throw new Error('Search type provided not recognised');
// }
//
// if (type === SearchTypes.Transaction) {
// const hash = toNonHex(query);
// const request = await fetch(
// `${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
// );
//
// if (request?.ok) {
// const body: BlockExplorerTransaction = await request.json();
//
// if (body?.transaction) {
// return SearchTypes.Transaction;
// }
// }
// } else if (type === SearchTypes.Party) {
// const party = toNonHex(query);
//
// const request = await fetch(
// `${DATA_SOURCES.blockExplorerUrl}/transactions?limit=1&filters[tx.submitter]=${party}`
// );
//
// if (request.ok) {
// const body: BlockExplorerTransactions = await request.json();
//
// if (body?.transactions?.length) {
// return SearchTypes.Party;
// }
// }
// }
//
// return undefined;
// };
// export const getSearchType = async (
// query: string
// ): Promise<SearchTypes | undefined> => {
// const searchTypes = detectTypeFromQuery(query);
// const hasResults = searchTypes?.length;
//
// if (hasResults) {
// if (hasResults > 1) {
// const promises = searchTypes.map((type) =>
// detectTypeByFetching(query, type)
// );
// const results = await Promise.all(promises);
// return results.find((result) => result !== undefined);
// }
//
// return searchTypes[0];
// }
//
// return undefined;
// };
export const getSearchType = async ( export const getSearchType = async (
query: string query: string
): Promise<SearchTypes | undefined> => { ): Promise<SearchTypes | undefined> => {
@ -90,11 +133,7 @@ export const getSearchType = async (
if (hasResults) { if (hasResults) {
if (hasResults > 1) { if (hasResults > 1) {
const promises = searchTypes.map((type) => return await detectTypeByFetching(query);
detectTypeByFetching(query, type)
);
const results = await Promise.all(promises);
return results.find((type) => type !== undefined);
} }
return searchTypes[0]; return searchTypes[0];

View File

@ -22,32 +22,29 @@ export const Search = () => {
const query = fields.search; const query = fields.search;
if (!query) { if (!query) {
setError(new Error(t('Search query required'))); return setError(new Error(t('Search query required')));
} else {
const result = await getSearchType(query);
const urlAsHex = toHex(query);
const unrecognisedError = new Error(
t('Transaction type is not recognised')
);
if (result) {
switch (result) {
case SearchTypes.Party:
navigate(`${Routes.PARTIES}/${urlAsHex}`);
break;
case SearchTypes.Transaction:
navigate(`${Routes.TX}/${urlAsHex}`);
break;
case SearchTypes.Block:
navigate(`${Routes.BLOCKS}/${Number(query)}`);
break;
default:
setError(unrecognisedError);
}
}
setError(unrecognisedError);
} }
const result = await getSearchType(query);
const urlAsHex = toHex(query);
const unrecognisedError = new Error(
t('Transaction type is not recognised')
);
if (result) {
switch (result) {
case SearchTypes.Party:
return navigate(`${Routes.PARTIES}/${urlAsHex}`);
case SearchTypes.Transaction:
return navigate(`${Routes.TX}/${urlAsHex}`);
case SearchTypes.Block:
return navigate(`${Routes.BLOCKS}/${Number(query)}`);
default:
return setError(unrecognisedError);
}
}
return setError(unrecognisedError);
}, },
[navigate] [navigate]
); );
@ -61,22 +58,24 @@ export const Search = () => {
{t('Search by block number or transaction hash')} {t('Search by block number or transaction hash')}
</label> </label>
<div className="flex items-stretch gap-2"> <div className="flex items-stretch gap-2">
<Input <div className="flex grow relative">
{...register('search')} <Input
id="search" {...register('search')}
data-testid="search" id="search"
className="text-white" data-testid="search"
hasError={Boolean(error?.message)} className="text-white"
type="text" hasError={Boolean(error?.message)}
placeholder={t('Enter block number, party id or transaction hash')} type="text"
/> placeholder={t('Enter block number, party id or transaction hash')}
{error?.message && ( />
<div className="absolute top-[100%] flex-1 w-full"> {error?.message && (
<InputError data-testid="search-error" intent="danger"> <div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b">
{error.message} <InputError data-testid="search-error" intent="danger">
</InputError> {error.message}
</div> </InputError>
)} </div>
)}
</div>
<Button type="submit" size="sm" data-testid="search-button"> <Button type="submit" size="sm" data-testid="search-button">
{t('Search')} {t('Search')}
</Button> </Button>

View File

@ -2,7 +2,7 @@ import React from 'react';
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 { BlockExplorerTransaction } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import { toHex } from '../search/detect-search'; import { toHex } from '../search/detect-search';
const TRUNCATE_LENGTH = 14; const TRUNCATE_LENGTH = 14;
@ -13,7 +13,7 @@ export const TxsInfiniteListItem = ({
type, type,
block, block,
index, index,
}: Partial<BlockExplorerTransaction>) => { }: Partial<BlockExplorerTransactionResult>) => {
if ( if (
!hash || !hash ||
!submitter || !submitter ||

View File

@ -1,9 +1,9 @@
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'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
const generateTxs = (number: number): BlockExplorerTransaction[] => { const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
return Array.from(Array(number)).map((_) => ({ return Array.from(Array(number)).map((_) => ({
block: '87901', block: '87901',
index: 2, index: 2,

View File

@ -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 { BlockExplorerTransaction } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
interface TxsInfiniteListProps { interface TxsInfiniteListProps {
hasMoreTxs: boolean; hasMoreTxs: boolean;
areTxsLoading: boolean | undefined; areTxsLoading: boolean | undefined;
txs: BlockExplorerTransaction[] | undefined; txs: BlockExplorerTransactionResult[] | undefined;
loadMoreTxs: () => void; loadMoreTxs: () => void;
error: Error | undefined; error: Error | undefined;
className?: string; className?: string;
} }
interface ItemProps { interface ItemProps {
index: BlockExplorerTransaction; index: BlockExplorerTransactionResult;
style: React.CSSProperties; style: React.CSSProperties;
isLoading: boolean; isLoading: boolean;
error: Error | undefined; error: Error | undefined;

View File

@ -13,12 +13,13 @@ import { SubHeading } from '../../../components/sub-heading';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { Panel } from '../../../components/panel'; import { Panel } from '../../../components/panel';
import { InfoPanel } from '../../../components/info-panel'; import { InfoPanel } from '../../../components/info-panel';
import { toNonHex } from '../../../components/search/detect-search';
import { DATA_SOURCES } from '../../../config'; import { DATA_SOURCES } from '../../../config';
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
import type { import type {
PartyAssetsQuery, PartyAssetsQuery,
PartyAssetsQueryVariables, PartyAssetsQueryVariables,
} from './__generated__/PartyAssetsQuery'; } from './__generated__/PartyAssetsQuery';
import type { BlockExplorerTransactions } from '../../../routes/types/block-explorer-response';
const PARTY_ASSETS_QUERY = gql` const PARTY_ASSETS_QUERY = gql`
query PartyAssetsQuery($partyId: ID!) { query PartyAssetsQuery($partyId: ID!) {
@ -57,13 +58,12 @@ const PARTY_ASSETS_QUERY = gql`
const Party = () => { const Party = () => {
const { party } = useParams<{ party: string }>(); const { party } = useParams<{ party: string }>();
const partyId = party ? toNonHex(party) : '';
const { const {
state: { data: partyData }, state: { data: partyData },
} = useFetch<TendermintSearchTransactionResponse>( } = useFetch<BlockExplorerTransactions>(
`${ `${DATA_SOURCES.blockExplorerUrl}/transactions?limit=1&filters[tx.submitter]=${partyId}`
DATA_SOURCES.tendermintUrl
}/tx_search?query="tx.submitter='${party?.replace('0x', '')}'"`
); );
const { data } = useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>( const { data } = useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(
@ -71,7 +71,7 @@ const Party = () => {
{ {
// Don't cache data for this query, party information can move quite quickly // Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
variables: { partyId: party?.replace('0x', '') || '' }, variables: { partyId },
skip: !party, skip: !party,
} }
); );

View File

@ -5,12 +5,12 @@ import { RouteTitle } from '../../../components/route-title';
import { BlocksRefetch } from '../../../components/blocks'; import { BlocksRefetch } from '../../../components/blocks';
import { TxsInfiniteList } from '../../../components/txs'; import { TxsInfiniteList } from '../../../components/txs';
import type { import type {
BlockExplorerTransaction, BlockExplorerTransactionResult,
BlockExplorerTransactions, BlockExplorerTransactions,
} from '../../../routes/types/block-explorer-response'; } from '../../../routes/types/block-explorer-response';
interface TxsStateProps { interface TxsStateProps {
txsData: BlockExplorerTransaction[]; txsData: BlockExplorerTransactionResult[];
hasMoreTxs: boolean; hasMoreTxs: boolean;
lastCursor: string; lastCursor: string;
} }

View File

@ -1,49 +1,43 @@
import React from 'react'; import React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useFetch } from '@vegaprotocol/react-helpers'; import { useFetch } from '@vegaprotocol/react-helpers';
import type { TendermintTransactionResponse } from '../tendermint-transaction-response.d';
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
import { DATA_SOURCES } from '../../../config'; import { DATA_SOURCES } from '../../../config';
import { RouteTitle } from '../../../components/route-title'; import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched'; import { RenderFetched } from '../../../components/render-fetched';
import { TxContent } from './tx-content'; import { TxContent } from './tx-content';
import { TxDetails } from './tx-details'; import { TxDetails } from './tx-details';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
import { toNonHex } from '../../../components/search/detect-search';
const Tx = () => { const Tx = () => {
const { txHash } = useParams<{ txHash: string }>(); const { txHash } = useParams<{ txHash: string }>();
const hash = txHash ? toNonHex(txHash) : '';
const { const {
state: { data: tTxData, loading: tTxLoading, error: tTxError }, state: { data, loading: tTxLoading, error: tTxError },
} = useFetch<TendermintTransactionResponse>( } = useFetch<BlockExplorerTransaction>(
`${DATA_SOURCES.tendermintUrl}/tx?hash=${txHash}` `${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
); );
const {
state: { data: ceTxData, loading: ceTxLoading, error: ceTxError },
} = useFetch<ChainExplorerTxResponse>(DATA_SOURCES.chainExplorerUrl, {
method: 'POST',
body: JSON.stringify({
tx_hash: txHash,
node_url: `${DATA_SOURCES.tendermintUrl}/`,
}),
});
return ( return (
<section> <section>
<RouteTitle>{t('Transaction details')}</RouteTitle> <RouteTitle>{t('Transaction details')}</RouteTitle>
<RenderFetched error={tTxError} loading={tTxLoading}> <RenderFetched error={tTxError} loading={tTxLoading}>
<TxDetails <>
className="mb-28" <TxDetails
txData={tTxData?.result} className="mb-28"
pubKey={ceTxData?.PubKey} txData={data?.transaction}
/> pubKey={data?.transaction.submitter}
</RenderFetched> />
<h2 className="text-2xl uppercase mb-4">{t('Transaction content')}</h2> <h2 className="text-2xl uppercase mb-4">
<RenderFetched error={ceTxError} loading={ceTxLoading}> {t('Transaction content')}
<TxContent data={ceTxData} /> </h2>
<TxContent data={data?.transaction} />
</>
</RenderFetched> </RenderFetched>
</section> </section>
); );

View File

@ -8,14 +8,14 @@ import {
TableRow, TableRow,
} from '../../../components/table'; } from '../../../components/table';
import { TxOrderType } from '../../../components/txs'; import { TxOrderType } from '../../../components/txs';
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
interface TxContentProps { interface TxContentProps {
data: ChainExplorerTxResponse | undefined; data: BlockExplorerTransactionResult | undefined;
} }
export const TxContent = ({ data }: TxContentProps) => { export const TxContent = ({ data }: TxContentProps) => {
if (!data?.Command) { if (!data?.command) {
return ( return (
<StatusMessage> <StatusMessage>
{t('Could not retrieve transaction content')} {t('Could not retrieve transaction content')}
@ -31,13 +31,13 @@ export const TxContent = ({ data }: TxContentProps) => {
{t('Type')} {t('Type')}
</TableHeader> </TableHeader>
<TableCell modifier="bordered"> <TableCell modifier="bordered">
<TxOrderType orderType={data.Type} /> <TxOrderType orderType={data.type} />
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableWithTbody> </TableWithTbody>
<h3 className="font-mono mb-8">{t('Decoded transaction content')}</h3> <h3 className="font-mono mb-8">{t('Decoded transaction content')}</h3>
<SyntaxHighlighter data={JSON.parse(data.Command)} /> <SyntaxHighlighter data={data.command} />
</> </>
); );
}; };

View File

@ -1,33 +1,24 @@
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { truncateByChars } from '@vegaprotocol/react-helpers'; import { TxDetails } from './tx-details';
import { TxDetails, txDetailsTruncateLength } from './tx-details'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { Result } from '../tendermint-transaction-response.d';
const pubKey = 'test'; const pubKey = 'test';
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283'; const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
const height = '52987'; const height = '52987';
const tx =
'ClMIpeWjmKnn77FNEPedA5J9QhJAOGI3YjQzMWNlMmNhNzc4MWMzMTQ1M2IyYjc0MWYwMTJlNzQ1MzBhZDhjMDgzODVkMWQ1YjRiY2VkMTJiNDc1MhKTAQqAATM5NDFlNmExMzQ3MGVhNTlhNGExNmQzMjRiYzlkZjI5YWZkMzYxMDRiZjQ5MzEwZWMxM2ZiOTMxNTM2NGY3ZjU2ZTQyOTJmYTAyZDlhNTBlZDc0OWE0ZjExMzJiNjM2ZTZmMzQ3YzQ2NjdkYmM5OThmYzcyZjYzYzQxMzU4ZTAzEgx2ZWdhL2VkMjU1MTkYAYB9AtI+QDA5MzFhOGZkOGNjOTM1NDU4ZjQ3MGU0MzVhMDU0MTQzODdjZWE2ZjMyOWQ2NDhiZTg5NGZjZDQ0YmQ1MTdhMmI=';
const txData = { const txData: BlockExplorerTransactionResult = {
hash, hash,
height, block: height,
tx,
index: 0, index: 0,
tx_result: { submitter: pubKey,
code: 0, code: 0,
data: null, cursor: `${height}.0`,
log: '', type: 'type',
info: '', command: {},
events: [],
gas_wanted: '0',
gas_used: '0',
codespace: '',
},
}; };
const renderComponent = (txData: Result) => ( const renderComponent = (txData: BlockExplorerTransactionResult) => (
<Router> <Router>
<TxDetails txData={txData} pubKey={pubKey} /> <TxDetails txData={txData} pubKey={pubKey} />
</Router> </Router>
@ -49,15 +40,6 @@ describe('Transaction details', () => {
expect(screen.getByText(height)).toBeInTheDocument(); expect(screen.getByText(height)).toBeInTheDocument();
}); });
it('Renders the truncated tx text', () => {
render(renderComponent(txData));
expect(
screen.getByText(
truncateByChars(tx, txDetailsTruncateLength, txDetailsTruncateLength)
)
).toBeInTheDocument();
});
it('Renders a copy button', () => { it('Renders a copy button', () => {
render(renderComponent(txData)); render(renderComponent(txData));
expect(screen.getByTestId('copy-tx-to-clipboard')).toBeInTheDocument(); expect(screen.getByTestId('copy-tx-to-clipboard')).toBeInTheDocument();

View File

@ -1,18 +1,18 @@
import { Routes } from '../../route-names'; import { Routes } from '../../route-names';
import { ButtonLink, CopyWithTooltip } from '@vegaprotocol/ui-toolkit'; import { CopyWithTooltip, Icon } from '@vegaprotocol/ui-toolkit';
import { import {
TableWithTbody, TableWithTbody,
TableCell, TableCell,
TableHeader, TableHeader,
TableRow, TableRow,
} from '../../../components/table'; } from '../../../components/table';
import { TruncateInline } from '../../../components/truncate/truncate';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { HighlightedLink } from '../../../components/highlighted-link'; import { HighlightedLink } from '../../../components/highlighted-link';
import type { Result } from '../tendermint-transaction-response.d'; import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import React from 'react';
interface TxDetailsProps { interface TxDetailsProps {
txData: Result | undefined; txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined; pubKey: string | undefined;
className?: string; className?: string;
} }
@ -21,7 +21,7 @@ export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => { export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
if (!txData) { if (!txData) {
return <>{t('Awaiting Tendermint transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
return ( return (
@ -30,6 +30,15 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
<TableCell>{t('Hash')}</TableCell> <TableCell>{t('Hash')}</TableCell>
<TableCell modifier="bordered" data-testid="hash"> <TableCell modifier="bordered" data-testid="hash">
{txData.hash} {txData.hash}
<CopyWithTooltip text={txData.hash}>
<button
title={t('Copy tx to clipboard')}
data-testid="copy-tx-to-clipboard"
className="underline"
>
<Icon name="duplicate" className="ml-2" />
</button>
</CopyWithTooltip>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered"> <TableRow modifier="bordered">
@ -44,31 +53,11 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
<TableCell>{t('Block')}</TableCell> <TableCell>{t('Block')}</TableCell>
<TableCell modifier="bordered" data-testid="block"> <TableCell modifier="bordered" data-testid="block">
<HighlightedLink <HighlightedLink
to={`/${Routes.BLOCKS}/${txData.height}`} to={`/${Routes.BLOCKS}/${txData.block}`}
text={txData.height} text={txData.block}
/> />
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Encoded txn')}</TableCell>
<TableCell
modifier="bordered"
data-testid="encoded-tnx"
className="flex justify-between"
>
<TruncateInline
text={txData.tx}
startChars={txDetailsTruncateLength}
endChars={txDetailsTruncateLength}
/>
<CopyWithTooltip text={txData.tx}>
<ButtonLink
title={t('Copy tx to clipboard')}
data-testid="copy-tx-to-clipboard"
/>
</CopyWithTooltip>
</TableCell>
</TableRow>
</TableWithTbody> </TableWithTbody>
); );
}; };

View File

@ -1,4 +1,4 @@
export interface BlockExplorerTransaction { export interface BlockExplorerTransactionResult {
block: string; block: string;
index: number; index: number;
hash: string; hash: string;
@ -10,5 +10,9 @@ export interface BlockExplorerTransaction {
} }
export interface BlockExplorerTransactions { export interface BlockExplorerTransactions {
transactions: BlockExplorerTransaction[]; transactions: BlockExplorerTransactionResult[];
}
export interface BlockExplorerTransaction {
transaction: BlockExplorerTransactionResult;
} }