feat(explorer): batch market instructions (#2617)

* feat(explorer): add basic table for all types in a batch
* feat(explorer): use marketlink for market ids in batch
* feat(explorer): add ordersummary
* feat(explorer): improve caching with some slight overfetching and cache config
This commit is contained in:
Edd 2023-01-17 09:51:42 +00:00 committed by GitHub
parent cd0ed05eb8
commit ff53e5e841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 525 additions and 84 deletions

View File

@ -1,23 +1,15 @@
import classnames from 'classnames';
import { useState, useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import { useLocation } from 'react-router-dom';
import {
EnvironmentProvider,
NetworkLoader,
useEnvironment,
} from '@vegaprotocol/environment';
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
import { NetworkInfo } from '@vegaprotocol/network-info';
import { Nav } from './components/nav';
import { Header } from './components/header';
import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import { ENV } from './config/env';
import type { InMemoryCacheConfig } from '@apollo/client';
function App() {
const { VEGA_ENV } = useEnvironment();
const [menuOpen, setMenuOpen] = useState(false);
const location = useLocation();
@ -26,18 +18,9 @@ function App() {
setMenuOpen(false);
}, [location]);
useEffect(() => {
Sentry.init({
dsn: ENV.dsn,
integrations: [new BrowserTracing()],
tracesSampleRate: 1,
environment: VEGA_ENV,
});
}, [VEGA_ENV]);
const cacheConfig: InMemoryCacheConfig = {
typePolicies: {
Node: {
statistics: {
keyFields: false,
},
},

View File

@ -18,7 +18,6 @@ const AssetBalance = ({
showAssetLink = true,
}: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id: assetId },
});

View File

@ -16,7 +16,6 @@ export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
*/
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({
fetchPolicy: 'cache-first',
variables: { id },
});

View File

@ -29,7 +29,7 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
label = (
<div title={t('Unknown market')}>
<span role="img" aria-label="Unknown market" className="img">
&nbsp;{t('Invalid market')}
</span>
&nbsp;{id}
</div>

View File

@ -22,8 +22,14 @@ fragment ExplorerDeterministicOrderFields on Order {
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
state
}
}

View File

@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } };
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } };
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order {
@ -38,8 +38,14 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
state
}
}
`;

View File

@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/react-helpers';
import type * as Schema from '@vegaprotocol/types';
import type { components } from '../../../../types/explorer';
export interface DeterministicOrderDetailsProps {
id: string;
@ -19,7 +20,8 @@ export const statusText: Record<Schema.OrderStatus, string> = {
STATUS_STOPPED: t('Stopped'),
};
export const sideText: Record<Schema.Side, string> = {
export const sideText: Record<components['schemas']['vegaSide'], string> = {
SIDE_UNSPECIFIED: t('Unspecified'),
SIDE_BUY: t('Buy'),
SIDE_SELL: t('Sell'),
};

View File

@ -0,0 +1,80 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import OrderSummary from './order-summary';
import type { OrderSummaryModifier } from './order-summary';
import { render } from '@testing-library/react';
import { ExplorerDeterministicOrderDocument } from '../order-details/__generated__/Order';
const mock = {
request: {
query: ExplorerDeterministicOrderDocument,
variables: {
orderId: '123',
},
},
result: {
data: {
orderByID: {
__typename: 'Order',
id: '123',
type: 'GTC',
status: 'OPEN',
version: '1',
createdAt: 'Tue, Jan 10, 2023 3:35',
expiresAt: 'Tue, Jan 10, 2023 3:35',
updatedAt: 'Tue, Jan 10, 2023 3:35',
rejectionReason: null,
reference: null,
timeInForce: 'GTC',
price: '333',
side: 'SIDE_BUY',
remaining: '100',
size: '100',
party: {
id: '456',
},
market: {
__typename: 'Market',
id: '789',
decimalPlaces: 2,
tradableInstrument: {
instrument: {
name: 'TEST',
},
},
},
},
},
},
};
function renderComponent(
id: string,
mocks?: MockedResponse[],
modifier?: OrderSummaryModifier
) {
return render(
<MockedProvider mocks={mocks}>
<OrderSummary id={id} modifier={modifier} />
</MockedProvider>
);
}
describe('Order Summary component', () => {
it('side, size are present', async () => {
const res = renderComponent(mock.result.data.orderByID.id, [mock]);
expect(await res.findByText('Buy')).toBeInTheDocument();
expect(await res.findByText('100')).toBeInTheDocument();
// Note: Market is not mocked so the PriceInMarket component is not rendering in this test - hence no price formatting
});
it('Cancelled modifier add strikethrough', async () => {
const res = renderComponent(
mock.result.data.orderByID.id,
[mock],
'cancelled'
);
const buy = await res.findByText('Buy');
expect(buy.parentElement).toHaveClass('line-through');
});
});

View File

@ -0,0 +1,50 @@
import { useExplorerDeterministicOrderQuery } from '../order-details/__generated__/Order';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
// Note: Edited has no style currently
export type OrderSummaryModifier = 'cancelled' | 'edited';
/**
* Provides Tailwind classnames to apply to order
* @param modifier
* @returns string
*/
export function getClassName(modifier?: OrderSummaryModifier) {
if (modifier === 'cancelled') {
return 'line-through';
}
return undefined;
}
export interface OrderSummaryProps {
id: string;
modifier?: OrderSummaryModifier;
}
/**
* This component renders the *current* details for an order, like OrderDetails but much
* more compact. It is equivalent to OrderTxSummary, but operates on an order rather than
* the transaction that creates an order
*/
const OrderSummary = ({ id, modifier }: OrderSummaryProps) => {
const { data, error } = useExplorerDeterministicOrderQuery({
variables: { orderId: id },
});
if (error || !data || (data && !data.orderByID)) {
return <div data-testid="order-summary">-</div>;
}
const order = data.orderByID;
return (
<div data-testid="order-summary" className={getClassName(modifier)}>
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i>@</i>&nbsp;
<PriceInMarket marketId={order.market.id} price={order.price} />
</div>
);
};
export default OrderSummary;

View File

@ -0,0 +1,119 @@
import type { MockedResponse } from '@apollo/client/testing';
import type { components } from '../../../types/explorer';
import type { UnknownObject } from '../nested-data-list';
import { MockedProvider } from '@apollo/client/testing';
import OrderTxSummary from './order-tx-summary';
import { render } from '@testing-library/react';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
type Order = components['schemas']['v1OrderSubmission'];
function renderComponent(order: Order, mocks?: MockedResponse[]) {
return render(
<MockedProvider mocks={mocks}>
<OrderTxSummary order={order} />
</MockedProvider>
);
}
describe('Order TX Summary component', () => {
it('Renders nothing if the order passed is somehow null', () => {
const res = renderComponent({} as UnknownObject as Order);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order passed lacks a side', () => {
const o: Order = {
marketId: '123',
price: '100',
type: 'TYPE_LIMIT',
size: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order passed lacks a price', () => {
const o: Order = {
marketId: '123',
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order has an unspecified side', () => {
const o: Order = {
marketId: '123',
side: 'SIDE_UNSPECIFIED',
type: 'TYPE_LIMIT',
size: '10',
price: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('Renders nothing if the order has an unspecified market', () => {
const o: Order = {
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
price: '10',
};
const res = renderComponent(o);
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
});
it('side, size and price in market if all details are present', async () => {
const o: Order = {
marketId: '123',
side: 'SIDE_BUY',
type: 'TYPE_LIMIT',
size: '10',
price: '333',
};
const mock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'TEST',
product: {
__typename: 'Future',
quoteName: 'TEST',
},
},
},
},
},
},
};
const res = renderComponent(o, [mock]);
expect(res.queryByTestId('order-summary')).toBeInTheDocument();
expect(res.getByText('Buy')).toBeInTheDocument();
expect(res.getByText('10')).toBeInTheDocument();
// Initially renders price alone
expect(res.getByText('333')).toBeInTheDocument();
// After fetch renders formatted price and asset quotename
expect(await res.findByText('3.33')).toBeInTheDocument();
expect(await res.findByText('TEST')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,41 @@
import type { components } from '../../../types/explorer';
import PriceInMarket from '../price-in-market/price-in-market';
import { sideText } from '../order-details/lib/order-labels';
export type OrderSummaryProps = {
order: components['schemas']['v1OrderSubmission'];
};
/**
* Shows a brief, relatively plaintext summary of an order transaction
* showing the price, correctly formatted. Created for the Batch Submission
* list, should be kept compact.
*
* Market name is expected to be listed elsewhere, so is not included
*/
const OrderTxSummary = ({ order }: OrderSummaryProps) => {
// Render nothing if the Order Submission doesn't look right
if (
!order ||
!order.marketId ||
!order.price ||
!order.side ||
order.side === 'SIDE_UNSPECIFIED'
) {
return null;
}
return (
<div data-testid="order-summary">
<span>{sideText[order.side]}</span>&nbsp;
<span>{order.size}</span>&nbsp;<i className="text-xs">@</i>&nbsp;
<PriceInMarket
marketId={order.marketId}
price={order.price}
></PriceInMarket>
</div>
);
};
export default OrderTxSummary;

View File

@ -15,6 +15,7 @@ export type PriceInMarketProps = {
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
});
let label = price;
@ -37,9 +38,9 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
);
} else {
return (
<div className="inline-block">
<label>
<span>{label}</span> <span>{suffix}</span>
</div>
</label>
);
}
};

View File

@ -0,0 +1,29 @@
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderSummary from '../../../order-summary/order-summary';
interface BatchAmendProps {
index: number;
submission: BatchInstruction;
}
/**
* Table row for a single amendment in a batch submission
*/
export const BatchAmend = ({ index, submission }: BatchAmendProps) => {
return (
<tr key={`amend-${index}`}>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderAmendment'} />
</td>
<td>
<OrderSummary id={submission.orderId} modifier="edited" />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};

View File

@ -0,0 +1,29 @@
import type { BatchCancellationInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderSummary from '../../../order-summary/order-summary';
interface BatchCancelProps {
index: number;
submission: BatchCancellationInstruction;
}
/**
* Table row for a single cancellation in a batch submission
*/
export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
return (
<tr>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderCancellation'} />
</td>
<td>
<OrderSummary id={submission.orderId} modifier="cancelled" />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};

View File

@ -0,0 +1,29 @@
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderTxSummary from '../../../order-summary/order-tx-summary';
interface BatchOrderProps {
index: number;
submission: BatchInstruction;
}
/**
* Table row for a single order in a batch submission
*/
export const BatchOrder = ({ index, submission }: BatchOrderProps) => {
return (
<tr key={`batch-${index}`}>
<td>{index}</td>
<td>
<TxOrderType orderType={'OrderSubmission'} />
</td>
<td>
<OrderTxSummary order={submission} />
</td>
<td>
<MarketLink id={submission.marketId} />
</td>
</tr>
);
};

View File

@ -31,6 +31,10 @@ export const ChainResponseCode = ({
const icon = isSuccess ? '✅' : '❌';
const label = ErrorCodes.get(code) || 'Unknown response code';
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
const displayError =
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return (
<div title={`Response code: ${code} - ${label}`}>
<span
@ -41,8 +45,8 @@ export const ChainResponseCode = ({
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!error ? (
<span className="ml-1">&mdash;&nbsp;{error}</span>
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">&mdash;&nbsp;{displayError}</span>
) : null}
</div>
);

View File

@ -14,6 +14,12 @@ interface TxDetailsSharedProps {
blockData: TendermintBlocksResponse | undefined;
}
// Applied to all header cells
const sharedHeaderProps = {
// Ensures that multi line contents still have the header aligned to the first line
className: 'align-top',
};
/**
* These rows are shown for every transaction type, providing a consistent set of rows for the top
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
@ -34,27 +40,27 @@ export const TxDetailsShared = ({
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Type')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{txData.type}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Hash')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>
<code>{txData.hash}</code>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Submitter')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Submitter')}</TableCell>
<TableCell>{pubKey ? <PartyLink id={pubKey} /> : '-'}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Block')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
<TableCell>
<BlockLink height={height} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Time')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
<TableCell>
{time ? (
<div>
@ -71,7 +77,7 @@ export const TxDetailsShared = ({
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Response code')}</TableCell>
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
<TableCell>
<ChainResponseCode code={txData.code} error={txData.error} />
</TableCell>

View File

@ -1,8 +1,15 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type {
BatchCancellationInstruction,
BatchInstruction,
BlockExplorerTransactionResult,
} from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableWithTbody, TableRow, TableCell } from '../../table';
import { TableWithTbody, TableRow, TableCell, Table } from '../../table';
import { BatchCancel } from './batch-submission/batch-cancel';
import { BatchAmend } from './batch-submission/batch-amend';
import { BatchOrder } from './batch-submission/batch-order';
interface TxDetailsBatchProps {
txData: BlockExplorerTransactionResult | undefined;
@ -16,6 +23,11 @@ interface TxDetailsBatchProps {
*
* Design considerations for batch:
* - So far it's very basic details about the size of the batch
*
* Batches are processed in the following order:
* - Cancellations
* - Amends
* - Submissions
*/
export const TxDetailsBatch = ({
txData,
@ -26,47 +38,80 @@ export const TxDetailsBatch = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const countSubmissions =
txData.command.batchMarketInstructions.submissions?.length || 0;
const countAmendments =
txData.command.batchMarketInstructions.amendments?.length || 0;
const countCancellations =
txData.command.batchMarketInstructions.cancellations?.length || 0;
const countTotal = countSubmissions + countAmendments + countCancellations;
const submissions: BatchInstruction[] =
txData.command.batchMarketInstructions.submissions;
const countSubmissions = submissions?.length || 0;
const amendments: BatchInstruction[] =
txData.command.batchMarketInstructions.amendments;
const countAmendments = amendments?.length || 0;
const cancellations: BatchCancellationInstruction[] =
txData.command.batchMarketInstructions.cancellations;
const countCancellations = cancellations.length || 0;
const countTotal = countSubmissions + countAmendments + countCancellations;
let index = 0;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
<span>{countTotal}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Submissions')}</span>
</TableCell>
<TableCell>
<span>{countSubmissions}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Amendments')}</span>
</TableCell>
<TableCell>
<span>{countAmendments}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Cancellations')}</span>
</TableCell>
<TableCell>
<span>{countCancellations}</span>
</TableCell>
</TableRow>
</TableWithTbody>
<div key={`tx-${index}`}>
<TableWithTbody className="mb-8">
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Batch size')}</TableCell>
<TableCell>
<span>{countTotal}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Cancellations')}</span>
</TableCell>
<TableCell>
<span>{countCancellations}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Amendments')}</span>
</TableCell>
<TableCell>
<span>{countAmendments}</span>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>
<span className="ml-5">{t('Submissions')}</span>
</TableCell>
<TableCell>
<span>{countSubmissions}</span>
</TableCell>
</TableRow>
</TableWithTbody>
<Table className="max-w-5xl min-w-fit">
<thead>
<TableRow modifier="bordered" className="font-mono">
<th align="left">{t('#')}</th>
<th align="left">{t('Type')}</th>
<th align="left">{t('Order')}</th>
<th align="left">{t('Market')}</th>
</TableRow>
</thead>
<tbody>
{cancellations.map((c) => (
<BatchCancel key={`bc-${index}`} submission={c} index={index++} />
))}
{amendments.map((a) => (
<BatchAmend key={`ba-${index}`} submission={a} index={index++} />
))}
{submissions.map((s) => (
<BatchOrder key={`bo-${index}`} submission={s} index={index++} />
))}
</tbody>
</Table>
</div>
);
};

View File

@ -45,7 +45,7 @@ export const TxDetailsWrapper = ({
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
return (
<>
<div key={`txd-${txData.hash}`}>
<section>{child({ txData, pubKey, blockData })}</section>
<details title={t('Decoded transaction')} className="mt-3">
@ -59,7 +59,7 @@ export const TxDetailsWrapper = ({
<code className="break-all font-mono text-xs">{raw}</code>
</details>
) : null}
</>
</div>
);
};

View File

@ -15,9 +15,15 @@ fragment ExplorerPartyAssetsAccounts on AccountBalance {
balance
market {
id
decimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
}

View File

@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerPartyAssetsAccountsFragment = { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null };
export type ExplorerPartyAssetsAccountsFragment = { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null } } | null> | null } | null } }> } | null };
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance {
@ -30,9 +30,15 @@ export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
balance
market {
id
decimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
}
}
}
}
}

View File

@ -51,6 +51,7 @@ export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
return (
<TableRow
key={`pa-${account.asset.id}-${account.type}`}
title={account.asset.name}
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
>

View File

@ -51,12 +51,12 @@ const Party = () => {
const staking = (
<section>
{p?.stakingSummary?.currentStakeAvailable ? (
<p className="mt-4 leading-3">
<div className="mt-4 leading-3">
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</p>
</div>
) : null}
</section>
);

View File

@ -15,7 +15,7 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<section className="mb-10">
<section className="mb-10" key={txData.hash}>
<TxDetailsWrapper height={txData.block} txData={txData} pubKey={pubKey} />
</section>
);