Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f00feb043a | ||
|
|
8848896976 | ||
|
|
62c26c71b3 | ||
|
|
f41d91615c | ||
|
|
89862b5299 | ||
|
|
c3cbb71c21 | ||
|
|
4406c91877 | ||
|
|
5ed2d699f2 | ||
|
|
9fbcda8181 | ||
|
|
7ed042a252 | ||
|
|
f3f662cc2f | ||
|
|
67f2a7f478 | ||
|
|
bd8e33dacd | ||
|
|
dc00592939 | ||
|
|
87b41a30d8 | ||
|
|
0850f31855 | ||
|
|
43aff8e359 | ||
|
|
6e9e7c2a5c | ||
|
|
f054f4c516 | ||
|
|
f382078ee6 | ||
|
|
ebc058bcbe | ||
|
|
d3df339696 | ||
|
|
a31008ea26 | ||
|
|
5e93e98f07 | ||
|
|
bf3ff8fb6f | ||
|
|
16538ca3a3 | ||
|
|
2fa00dacaa | ||
|
|
45b7c2ad4d |
@@ -228,6 +228,22 @@ jobs:
|
||||
AWS_REGION: 'eu-west-1'
|
||||
SOURCE_DIR: 'dist-result'
|
||||
|
||||
- name: Install aws CLI
|
||||
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
|
||||
uses: unfor19/install-aws-cli-action@master
|
||||
|
||||
- name: Perform cache invalidation
|
||||
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: 'eu-west-1'
|
||||
run: |
|
||||
echo "Looking for distribution for bucket: ${{ env.BUCKET_NAME }}"
|
||||
id=$(aws cloudfront list-distributions | jq -Mrc '.DistributionList.Items | .[] | select(.DefaultCacheBehavior.TargetOriginId == "${{ env.BUCKET_NAME }}") | .Id')
|
||||
echo "Found id is: ${id}"
|
||||
aws cloudfront create-invalidation --distribution-id $id --paths "/*"
|
||||
|
||||
- name: Add preview label
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
|
||||
import { useRef, useLayoutEffect } from 'react';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
|
||||
type AssetsTableProps = {
|
||||
data: AssetFieldsFragment[] | null;
|
||||
@@ -31,6 +31,58 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: t('Symbol'), field: 'symbol' },
|
||||
{ headerName: t('Name'), field: 'name' },
|
||||
{
|
||||
flex: 2,
|
||||
headerName: t('ID'),
|
||||
field: 'id',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
},
|
||||
{
|
||||
colId: 'type',
|
||||
headerName: t('Type'),
|
||||
field: 'source.__typename',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
valueFormatter: ({ value }: { value?: string }) =>
|
||||
value ? AssetTypeMapping[value].value : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'status',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
valueFormatter: ({ value }: { value?: string }) =>
|
||||
value ? AssetStatusMapping[value].value : '',
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
headerName: '',
|
||||
sortable: false,
|
||||
filter: false,
|
||||
resizable: false,
|
||||
wrapText: true,
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
navigate(value);
|
||||
}}
|
||||
>
|
||||
{t('View details')}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
},
|
||||
],
|
||||
[navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
@@ -46,60 +98,11 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data }: RowClickedEvent) => {
|
||||
navigate(data.id);
|
||||
}}
|
||||
>
|
||||
<AgGridColumn headerName={t('Symbol')} field="symbol" />
|
||||
<AgGridColumn headerName={t('Name')} field="name" />
|
||||
<AgGridColumn
|
||||
flex="2"
|
||||
headerName={t('ID')}
|
||||
field="id"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="type"
|
||||
headerName={t('Type')}
|
||||
field="source.__typename"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
valueFormatter={({ value }: { value?: string }) =>
|
||||
value && AssetTypeMapping[value].value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="status"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
valueFormatter={({ value }: { value?: string }) =>
|
||||
value && AssetStatusMapping[value].value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
headerName=""
|
||||
sortable={false}
|
||||
filter={false}
|
||||
resizable={false}
|
||||
wrapText={true}
|
||||
field="id"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
navigate(value);
|
||||
}}
|
||||
>
|
||||
{t('View details')}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
export interface InfoBlockProps {
|
||||
title: string;
|
||||
|
||||
@@ -1,33 +1,98 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import PartyLink from './party-link';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { ExplorerNodeNamesDocument } from '../../../routes/validators/__generated__/NodeNames';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
const zeroes =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: ExplorerNodeNamesDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
nodesConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
name: 'Validator Node',
|
||||
pubkey:
|
||||
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e',
|
||||
tmPubkey: 'tmPubkey1',
|
||||
ethereumAddress: '0x123456789',
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
name: 'Node 2',
|
||||
pubkey: 'pubkey2',
|
||||
tmPubkey: 'tmPubkey2',
|
||||
ethereumAddress: '0xabcdef123',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('PartyLink', () => {
|
||||
it('renders Network for 000.000 party', () => {
|
||||
const zeroes =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const screen = render(<PartyLink id={zeroes} />);
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<PartyLink id={zeroes} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Network for network party', () => {
|
||||
const screen = render(<PartyLink id="network" />);
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<PartyLink id="network" />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders ID with no link for invalid party', () => {
|
||||
const screen = render(<PartyLink id="this-party-is-not-valid" />);
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<PartyLink id="this-party-is-not-valid" />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByTestId('invalid-party')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('if the key is a validator, render their name instead', async () => {
|
||||
const screen = render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MemoryRouter>
|
||||
<PartyLink id="13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e" />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// Wait for hook to update with mock data
|
||||
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
|
||||
await expect(screen.getByText('Validator Node')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links a valid party to the party page', () => {
|
||||
const aValidParty =
|
||||
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e';
|
||||
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<PartyLink id={aValidParty} />
|
||||
</MemoryRouter>
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<PartyLink id={aValidParty} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const el = screen.getByText(aValidParty);
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useMemo, type ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error';
|
||||
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import { useExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
|
||||
import type { ExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
|
||||
|
||||
export const SPECIAL_CASE_NETWORK_ID =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
export const SPECIAL_CASE_NETWORK = 'network';
|
||||
|
||||
export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
|
||||
if (!data || data?.nodesConnection?.edges?.length === 0) {
|
||||
return id;
|
||||
}
|
||||
|
||||
const validator = data.nodesConnection.edges?.find((e) => {
|
||||
return e?.node.pubkey === id;
|
||||
});
|
||||
|
||||
if (validator) {
|
||||
return validator.node.name;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
const { data } = useExplorerNodeNamesQuery();
|
||||
const name = useMemo(() => getNameForParty(id, data), [data, id]);
|
||||
const useName = name !== id;
|
||||
|
||||
// Some transactions will involve the 'network' party, which is alias for '000...000'
|
||||
// The party page does not handle this nicely, so in this case we render the word 'Network'
|
||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||
@@ -38,13 +60,20 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
<Hash text={truncate ? truncateMiddle(id) : id} />
|
||||
</Link>
|
||||
<span className="whitespace-nowrap">
|
||||
{useName && <Icon size={4} name="cube" className="mr-2" />}
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
{useName ? (
|
||||
name
|
||||
) : (
|
||||
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
|
||||
)}
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { MarketFieldsFragment } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
@@ -39,54 +40,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
|
||||
navigate(data.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
colId="code"
|
||||
headerName={t('Code')}
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="name"
|
||||
headerName={t('Name')}
|
||||
field="tradableInstrument.instrument.name"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="state"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
valueGetter={({
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
colId: 'code',
|
||||
headerName: t('Code'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
},
|
||||
{
|
||||
colId: 'name',
|
||||
headerName: t('Name'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketFieldsFragment>) => {
|
||||
return data?.state ? MarketStateMapping[data?.state] : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="asset"
|
||||
headerName={t('Settlement asset')}
|
||||
field="tradableInstrument.instrument.product.settlementAsset.symbol"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'asset',
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketFieldsFragment,
|
||||
@@ -105,19 +86,19 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
) : (
|
||||
''
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
flex={2}
|
||||
headerName={t('Market ID')}
|
||||
field="id"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
headerName=""
|
||||
field="id"
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
flex: 2,
|
||||
headerName: t('Market ID'),
|
||||
field: 'id',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
headerName: '',
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
@@ -126,9 +107,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
</Link>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
),
|
||||
},
|
||||
],
|
||||
[openAssetDetailsDialog]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
|
||||
navigate(data.id);
|
||||
}
|
||||
/>
|
||||
</AgGrid>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,10 @@ fragment ExplorerDeterministicOrderFields on Order {
|
||||
remaining
|
||||
size
|
||||
rejectionReason
|
||||
peggedOrder {
|
||||
reference
|
||||
offset
|
||||
}
|
||||
party {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -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, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: 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, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: 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, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: 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, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: 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 {
|
||||
@@ -29,6 +29,10 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
remaining
|
||||
size
|
||||
rejectionReason
|
||||
peggedOrder {
|
||||
reference
|
||||
offset
|
||||
}
|
||||
party {
|
||||
id
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type Amend = components['schemas']['v1OrderAmendment'];
|
||||
|
||||
function renderAmendOrderDetails(
|
||||
id: string,
|
||||
version: number,
|
||||
version: number | undefined,
|
||||
amend: Amend,
|
||||
mocks: MockedResponse[]
|
||||
) {
|
||||
@@ -25,7 +25,11 @@ function renderAmendOrderDetails(
|
||||
);
|
||||
}
|
||||
|
||||
function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
function renderExistingAmend(
|
||||
id: string,
|
||||
version: number | undefined,
|
||||
amend: Amend
|
||||
) {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
@@ -49,6 +53,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
price: '200',
|
||||
side: 'BUY',
|
||||
peggedOrder: null,
|
||||
remaining: '99',
|
||||
rejectionReason: 'rejection',
|
||||
reference: '123',
|
||||
@@ -77,6 +82,56 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: ExplorerDeterministicOrderDocument,
|
||||
variables: {
|
||||
orderId: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
orderByID: {
|
||||
__typename: 'Order',
|
||||
id: '123',
|
||||
type: 'GTT',
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
version: 100,
|
||||
createdAt: '123',
|
||||
updatedAt: '456',
|
||||
expiresAt: '789',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
peggedOrder: null,
|
||||
price: '200',
|
||||
side: 'BUY',
|
||||
remaining: '99',
|
||||
rejectionReason: 'rejection',
|
||||
reference: '123',
|
||||
size: '200',
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: '234',
|
||||
},
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'amend-to-order-latest-version',
|
||||
state: 'STATUS_ACTIVE',
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: '5',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'amend-to-order-latest-version-test',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
@@ -157,4 +212,15 @@ describe('Amend order details', () => {
|
||||
expect(await res.findByText('New price')).toBeInTheDocument();
|
||||
expect(await res.findByText('-7879')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Fetches latest version when version is not specified', async () => {
|
||||
const amend: Amend = {
|
||||
price: '-7879',
|
||||
};
|
||||
|
||||
const res = renderExistingAmend('123', undefined, amend);
|
||||
expect(
|
||||
await res.findByText('amend-to-order-latest-version')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import { wrapperClasses } from './deterministic-order-details';
|
||||
export interface AmendOrderDetailsProps {
|
||||
id: string;
|
||||
amend: components['schemas']['v1OrderAmendment'];
|
||||
// Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0
|
||||
// Version to fetch. Latest is provided by default
|
||||
version?: number;
|
||||
}
|
||||
|
||||
@@ -34,13 +34,11 @@ export function getSideDeltaColour(delta: string): string {
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
const AmendOrderDetails = ({
|
||||
id,
|
||||
version = 0,
|
||||
amend,
|
||||
}: AmendOrderDetailsProps) => {
|
||||
const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
const variables = version ? { orderId: id, version } : { orderId: id };
|
||||
|
||||
const { data, error } = useExplorerDeterministicOrderQuery({
|
||||
variables: { orderId: id, version },
|
||||
variables,
|
||||
});
|
||||
|
||||
if (error || (data && !data.orderByID)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import PriceInMarket from '../price-in-market/price-in-market';
|
||||
import { Time } from '../time';
|
||||
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
||||
import SizeInMarket from '../size-in-market/size-in-market';
|
||||
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
@@ -68,25 +69,35 @@ const DeterministicOrderDetails = ({
|
||||
<span className="mx-5 text-base">@</span>
|
||||
<PriceInMarket price={o.price} marketId={o.market.id} />
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-4">
|
||||
<p className="text-gray-200">
|
||||
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
||||
</p>
|
||||
{o.peggedOrder ? (
|
||||
<p className="text-gray-200">
|
||||
{t('Price peg')}:{' '}
|
||||
<TxOrderPeggedReference
|
||||
side={o.side}
|
||||
reference={o.peggedOrder.reference}
|
||||
offset={o.peggedOrder.offset}
|
||||
marketId={o.market.id}
|
||||
/>
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{o.reference ? (
|
||||
<p className="text-gray-500 mb-4">
|
||||
<p className="text-gray-500 mt-4">
|
||||
<span>{t('Reference')}</span>: {o.reference}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid md:grid-cols-4 gap-x-6">
|
||||
{version !== 0 ? null : (
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Status')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||
{statusText[o.status]}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid md:grid-cols-4 gap-x-6 mt-4">
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Status')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||
{statusText[o.status]}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
||||
@@ -95,17 +106,6 @@ const DeterministicOrderDetails = ({
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{version !== 0 ? null : (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Remaining')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<SizeInMarket size={o.remaining} marketId={o.market.id} />
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Version')}
|
||||
|
||||
@@ -31,6 +31,7 @@ const mock = {
|
||||
side: 'SIDE_BUY',
|
||||
remaining: '100',
|
||||
size: '100',
|
||||
peggedOrder: null,
|
||||
party: {
|
||||
id: '456',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
@@ -9,7 +8,7 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -64,7 +63,128 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
title: '',
|
||||
content: null,
|
||||
});
|
||||
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
colId: 'title',
|
||||
headerName: t('Title'),
|
||||
field: 'rationale.title',
|
||||
flex: 2,
|
||||
wrapText: true,
|
||||
},
|
||||
{
|
||||
colId: 'type',
|
||||
maxWidth: 180,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Type'),
|
||||
field: 'terms.change.__typename',
|
||||
},
|
||||
{
|
||||
maxWidth: 100,
|
||||
headerName: t('State'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
maxWidth: 100,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Voting'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="uppercase flex h-full items-center justify-center pt-2">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'cDate',
|
||||
maxWidth: 150,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Closing date'),
|
||||
field: 'terms.closingDatetime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.closingDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'eDate',
|
||||
maxWidth: 150,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Enactment date'),
|
||||
field: 'terms.enactmentDatetime',
|
||||
valueFormatte: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.enactmentDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
minWidth: window.innerWidth > BREAKPOINT_MD ? 221 : 80,
|
||||
maxWidth: 221,
|
||||
sortable: false,
|
||||
filter: false,
|
||||
resizable: false,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
const proposalPage = tokenLink(
|
||||
TOKEN_PROPOSAL.replace(':id', data?.id || '')
|
||||
);
|
||||
const openDialog = () => {
|
||||
if (!data) return;
|
||||
setDialog({
|
||||
open: true,
|
||||
title: data.rationale.title,
|
||||
content: data.terms,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="pb-1">
|
||||
<button className="underline max-md:hidden" onClick={openDialog}>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ExternalLink className="max-md:hidden" href={proposalPage}>
|
||||
{t('Open in Governance')}
|
||||
</ExternalLink>
|
||||
<ExternalLink className="md:hidden" href={proposalPage}>
|
||||
{t('Open')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[requiredMajorityPercentage, tokenLink]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
@@ -83,6 +203,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if (
|
||||
@@ -94,128 +215,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
window.open(proposalPage, '_blank');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
colId="title"
|
||||
headerName={t('Title')}
|
||||
field="rationale.title"
|
||||
flex={2}
|
||||
wrapText={true}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="type"
|
||||
maxWidth={180}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Type')}
|
||||
field="terms.change.__typename"
|
||||
/>
|
||||
<AgGridColumn
|
||||
maxWidth={100}
|
||||
headerName={t('State')}
|
||||
field="state"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="voting"
|
||||
maxWidth={100}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Voting')}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="uppercase flex h-full items-center justify-center pt-2">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="cDate"
|
||||
maxWidth={150}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Closing date')}
|
||||
field="terms.closingDatetime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.closingDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="eDate"
|
||||
maxWidth={150}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Enactment date')}
|
||||
field="terms.enactmentDatetime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.enactmentDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80}
|
||||
maxWidth={221}
|
||||
sortable={false}
|
||||
filter={false}
|
||||
resizable={false}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
const proposalPage = tokenLink(
|
||||
TOKEN_PROPOSAL.replace(':id', data?.id || '')
|
||||
);
|
||||
const openDialog = () => {
|
||||
if (!data) return;
|
||||
setDialog({
|
||||
open: true,
|
||||
title: data.rationale.title,
|
||||
content: data.terms,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="pb-1">
|
||||
<button
|
||||
className="underline max-md:hidden"
|
||||
onClick={openDialog}
|
||||
>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ExternalLink className="max-md:hidden" href={proposalPage}>
|
||||
{t('Open in Governance')}
|
||||
</ExternalLink>
|
||||
<ExternalLink className="md:hidden" href={proposalPage}>
|
||||
{t('Open')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
<JsonViewerDialog
|
||||
open={dialog.open}
|
||||
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
|
||||
|
||||
+36
-17
@@ -1,4 +1,4 @@
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
|
||||
export const ErrorCodes = new Map([
|
||||
@@ -17,6 +17,8 @@ interface ChainResponseCodeProps {
|
||||
code: number;
|
||||
hideLabel?: boolean;
|
||||
error?: string;
|
||||
hideIfOk?: boolean;
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,14 +30,21 @@ export const ChainResponseCode = ({
|
||||
code,
|
||||
hideLabel = false,
|
||||
error,
|
||||
hideIfOk = false,
|
||||
small = false,
|
||||
}: ChainResponseCodeProps) => {
|
||||
if (hideIfOk && code === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isSuccess = successCodes.has(code);
|
||||
const size = small ? 3 : 4;
|
||||
const successColour =
|
||||
code === 71 ? 'fill-vega-orange' : 'fill-vega-green-600';
|
||||
code === 71 ? '!fill-vega-orange' : '!fill-vega-green-600';
|
||||
const icon = isSuccess ? (
|
||||
<Icon name="tick-circle" className={successColour} />
|
||||
<Icon size={size} name="tick-circle" className={`${successColour}`} />
|
||||
) : (
|
||||
<Icon name="cross" className="fill-vega-pink-600" />
|
||||
<Icon size={size} name="cross" className="!fill-vega-pink-500" />
|
||||
);
|
||||
const label = ErrorCodes.get(code) || 'Unknown response code';
|
||||
|
||||
@@ -44,18 +53,28 @@ export const ChainResponseCode = ({
|
||||
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
|
||||
|
||||
return (
|
||||
<div title={`Response code: ${code} - ${label}`} className=" inline-block">
|
||||
<span
|
||||
className="mr-2"
|
||||
aria-label={isSuccess ? 'Success' : 'Warning'}
|
||||
role="img"
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
{hideLabel ? null : <span>{label}</span>}
|
||||
{!hideLabel && !!displayError ? (
|
||||
<span className="ml-1 whitespace-pre">— {displayError}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<Tooltip
|
||||
description={
|
||||
<span>
|
||||
Response code: {code} - {label}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="mt-[-1px] inline-block">
|
||||
<span
|
||||
className="mr-2"
|
||||
aria-label={isSuccess ? 'Success' : 'Warning'}
|
||||
role="img"
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
{hideLabel ? null : <span>{label}</span>}
|
||||
{!hideLabel && !!displayError ? (
|
||||
<span className="ml-1 whitespace-pre">
|
||||
— {displayError}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import type { TxDetailsOrderProps } from './tx-order-peg';
|
||||
import { TxOrderPeggedReference, getMarketDecimals } from './tx-order-peg';
|
||||
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
|
||||
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
|
||||
import { PeggedReference, Side } from '@vegaprotocol/types';
|
||||
|
||||
// Mock the useExplorerMarketQuery hook
|
||||
jest.mock('../../../links/market-link/__generated__/Market', () => ({
|
||||
useExplorerMarketQuery: jest.fn().mockReturnValue({
|
||||
data: {
|
||||
market: { decimalPlaces: 0 },
|
||||
},
|
||||
loading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('getSettlementAsset', () => {
|
||||
it('should return the decimal places if data is defined', () => {
|
||||
const data = {
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '123',
|
||||
decimalPlaces: 8,
|
||||
},
|
||||
};
|
||||
|
||||
const result = getMarketDecimals(data as Partial<ExplorerMarketQuery>);
|
||||
|
||||
expect(result).toEqual(8);
|
||||
});
|
||||
|
||||
it('should return 0 if data is undefined', () => {
|
||||
const result = getMarketDecimals(undefined);
|
||||
|
||||
expect(result).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TxOrderPeggedReference', () => {
|
||||
beforeEach(() => {
|
||||
// Mock the useExplorerMarketQuery hook return value
|
||||
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
settlementAsset: 'some-settlement-asset',
|
||||
},
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should render the offset and reference correctly', () => {
|
||||
const props: TxDetailsOrderProps = {
|
||||
side: Side.SIDE_BUY,
|
||||
offset: '10',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
marketId: 'some-market-id',
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<TxOrderPeggedReference {...props} />);
|
||||
|
||||
expect(getByTestId('pegged-reference')).toHaveTextContent('Mid + 10');
|
||||
});
|
||||
|
||||
it('should return null if the reference is "PEGGED_REFERENCE_UNSPECIFIED"', () => {
|
||||
const props: TxDetailsOrderProps = {
|
||||
side: Side.SIDE_BUY,
|
||||
offset: '10',
|
||||
reference: 'PEGGED_REFERENCE_UNSPECIFIED',
|
||||
marketId: 'some-market-id',
|
||||
};
|
||||
|
||||
const { container } = render(<TxOrderPeggedReference {...props} />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the offset without formatting initially, then render the formatted version', () => {
|
||||
const props: TxDetailsOrderProps = {
|
||||
side: Side.SIDE_BUY,
|
||||
offset: '10',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
||||
marketId: 'some-market-id',
|
||||
};
|
||||
|
||||
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
|
||||
data: null,
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const screen = render(<TxOrderPeggedReference {...props} />);
|
||||
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
|
||||
'Ask + 10'
|
||||
);
|
||||
|
||||
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
market: {
|
||||
decimalPlaces: 10,
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
});
|
||||
|
||||
screen.rerender(<TxOrderPeggedReference {...props} />);
|
||||
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
|
||||
'Ask + 0.000000001'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TableCell, TableRow } from '../../../table';
|
||||
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
|
||||
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
|
||||
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
|
||||
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
export interface TxDetailsOrderProps {
|
||||
offset: string;
|
||||
reference: VegaPeggedReference;
|
||||
marketId: string;
|
||||
side: Side;
|
||||
}
|
||||
|
||||
export function getMarketDecimals(
|
||||
data: ExplorerMarketQuery | undefined
|
||||
): number {
|
||||
return data?.market?.decimalPlaces || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarises an order's peg
|
||||
*/
|
||||
export const TxOrderPeggedReferenceRow = ({
|
||||
offset,
|
||||
reference,
|
||||
marketId,
|
||||
side,
|
||||
}: TxDetailsOrderProps) => {
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Pegged order')}</TableCell>
|
||||
<TableCell>
|
||||
<TxOrderPeggedReference
|
||||
side={side}
|
||||
offset={offset}
|
||||
reference={reference}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export const TxOrderPeggedReference = ({
|
||||
offset,
|
||||
reference,
|
||||
marketId,
|
||||
side,
|
||||
}: TxDetailsOrderProps) => {
|
||||
const { data, loading } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
});
|
||||
|
||||
const direction = side === Side.SIDE_BUY ? '+' : '-';
|
||||
const decimalPlaces = getMarketDecimals(data);
|
||||
|
||||
if (reference === 'PEGGED_REFERENCE_UNSPECIFIED') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span data-testid="pegged-reference">
|
||||
{PeggedReferenceMapping[reference]}
|
||||
{direction}
|
||||
{!loading && data
|
||||
? addDecimalsFormatNumber(offset, decimalPlaces)
|
||||
: offset}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
|
||||
import Hash from '../../links/hash';
|
||||
import { TxOrderPeggedReferenceRow } from './order/tx-order-peg';
|
||||
|
||||
interface TxDetailsOrderProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -29,6 +30,8 @@ export const TxDetailsOrder = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
const marketId = txData.command.orderSubmission.marketId || '-';
|
||||
const reference = txData.command.orderSubmission.peggedOrder;
|
||||
const side = txData.command.orderSubmission.side;
|
||||
|
||||
let deterministicId = '';
|
||||
|
||||
@@ -63,6 +66,14 @@ export const TxDetailsOrder = ({
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{reference ? (
|
||||
<TxOrderPeggedReferenceRow
|
||||
side={side}
|
||||
offset={reference.offset}
|
||||
reference={reference.reference}
|
||||
marketId={marketId}
|
||||
/>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
{deterministicId.length > 0 ? (
|
||||
|
||||
@@ -10,13 +10,17 @@ export interface FilterLabelProps {
|
||||
*/
|
||||
export function FilterLabel({ filters }: FilterLabelProps) {
|
||||
if (!filters || filters.size !== 1) {
|
||||
return <span className="uppercase">{t('Filter')}</span>;
|
||||
return (
|
||||
<span data-testid="filter-empty" className="uppercase">
|
||||
{t('Filter')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="filter-selected">
|
||||
<span className="uppercase">{t('Filters')}:</span>
|
||||
<code className="bg-vega-light-150 px-2 rounded-md capitalize">
|
||||
<code className="bg-vega-light-150 dark:bg-vega-light-300 px-2 rounded-md capitalize dark:text-black">
|
||||
{Array.from(filters)[0]}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { TxsFilter } from './tx-filter';
|
||||
import type { FilterOption } from './tx-filter';
|
||||
|
||||
describe('TxsFilter', () => {
|
||||
it('renders holding text when nothing is selected', () => {
|
||||
const filters: Set<FilterOption> = new Set([]);
|
||||
const setFilters = jest.fn();
|
||||
render(<TxsFilter filters={filters} setFilters={setFilters} />);
|
||||
expect(screen.getByTestId('filter-empty')).toBeInTheDocument();
|
||||
expect(screen.getByText('Filter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the submit order filter as selected', () => {
|
||||
const filters: Set<FilterOption> = new Set(['Submit Order']);
|
||||
const setFilters = jest.fn();
|
||||
render(<TxsFilter filters={filters} setFilters={setFilters} />);
|
||||
expect(screen.getByTestId('filter-selected')).toBeInTheDocument();
|
||||
expect(screen.getByText('Submit Order')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -91,7 +91,7 @@ export interface TxFilterProps {
|
||||
* types. It allows a user to select one transaction type to view. Later
|
||||
* it will support multiple selection, but until the API supports that it is
|
||||
* one or all.
|
||||
* @param filters null or Set of tranaction types
|
||||
* @param filters null or Set of transaction types
|
||||
* @param setFilters A function to update the filters prop
|
||||
* @returns
|
||||
*/
|
||||
@@ -100,15 +100,15 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
trigger={
|
||||
<DropdownMenuTrigger className="ml-2">
|
||||
<Button size="xs">
|
||||
<DropdownMenuTrigger className="ml-0">
|
||||
<Button size="xs" data-testid="filter-trigger">
|
||||
<FilterLabel filters={filters} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{filters.size > 1 ? null : (
|
||||
{filters.size > 0 ? null : (
|
||||
<>
|
||||
<DropdownMenuCheckboxItem
|
||||
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { TxsListNavigation } from './tx-list-navigation';
|
||||
|
||||
const NOOP = () => {
|
||||
return;
|
||||
};
|
||||
describe('TxsListNavigation', () => {
|
||||
it('renders transaction list navigation', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={true}
|
||||
hasPreviousPage={true}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Newer')).toBeInTheDocument();
|
||||
expect(screen.getByText('Older')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls previousPage when "Newer" button is clicked', () => {
|
||||
const previousPageMock = jest.fn();
|
||||
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={previousPageMock}
|
||||
hasMoreTxs={true}
|
||||
hasPreviousPage={true}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Newer'));
|
||||
|
||||
expect(previousPageMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls nextPage when "Older" button is clicked', () => {
|
||||
const nextPageMock = jest.fn();
|
||||
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={nextPageMock}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={true}
|
||||
hasPreviousPage={true}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Older'));
|
||||
|
||||
expect(nextPageMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('disables "Older" button if hasMoreTxs is false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={false}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Older')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables "Newer" button if hasPreviousPage is false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={true}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Newer')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables both buttons when more and previous are false', () => {
|
||||
render(
|
||||
<TxsListNavigation
|
||||
refreshTxs={NOOP}
|
||||
nextPage={NOOP}
|
||||
previousPage={NOOP}
|
||||
hasMoreTxs={false}
|
||||
hasPreviousPage={false}
|
||||
>
|
||||
<span></span>
|
||||
</TxsListNavigation>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Newer')).toBeDisabled();
|
||||
expect(screen.getByText('Older')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { BlocksRefetch } from '../blocks';
|
||||
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface TxListNavigationProps {
|
||||
refreshTxs: () => void;
|
||||
nextPage: () => void;
|
||||
previousPage: () => void;
|
||||
loading?: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
hasMoreTxs: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
/**
|
||||
* Displays a list of transactions with filters and controls to navigate through the list.
|
||||
*
|
||||
* @returns {JSX.Element} Transaction List and controls
|
||||
*/
|
||||
export const TxsListNavigation = ({
|
||||
refreshTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
hasMoreTxs,
|
||||
hasPreviousPage,
|
||||
children,
|
||||
loading = false,
|
||||
}: TxListNavigationProps) => {
|
||||
return (
|
||||
<>
|
||||
<menu className="mb-2 w-full ">{children}</menu>
|
||||
<menu className="mb-2 w-full">
|
||||
<BlocksRefetch refetch={refreshTxs} />
|
||||
<div className="float-right">
|
||||
<Button
|
||||
className="mr-2"
|
||||
size="xs"
|
||||
disabled={!hasPreviousPage || loading}
|
||||
onClick={() => {
|
||||
previousPage();
|
||||
}}
|
||||
>
|
||||
{t('Newer')}
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
disabled={!hasMoreTxs || loading}
|
||||
onClick={() => {
|
||||
nextPage();
|
||||
}}
|
||||
>
|
||||
{t('Older')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="float-right mr-2">
|
||||
{loading ? (
|
||||
<span className="text-vega-light-300">{t('Loading...')}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -24,6 +24,7 @@ const displayString: StringMap = {
|
||||
LiquidityProvisionSubmission: 'LP order',
|
||||
'Liquidity Provision Order': 'LP order',
|
||||
LiquidityProvisionCancellation: 'LP cancel',
|
||||
'Cancel LiquidityProvision Order': 'LP cancel',
|
||||
LiquidityProvisionAmendment: 'LP update',
|
||||
'Amend LiquidityProvision Order': 'Amend LP',
|
||||
ProposalSubmission: 'Governance Proposal',
|
||||
@@ -36,9 +37,12 @@ const displayString: StringMap = {
|
||||
UndelegateSubmission: 'Undelegation',
|
||||
KeyRotateSubmission: 'Key Rotation',
|
||||
StateVariableProposal: 'State Variable',
|
||||
'State Variable Proposal': 'State Variable',
|
||||
Transfer: 'Transfer',
|
||||
CancelTransfer: 'Cancel Transfer',
|
||||
'Cancel Transfer Funds': 'Cancel Transfer',
|
||||
ValidatorHeartbeat: 'Heartbeat',
|
||||
'Validator Heartbeat': 'Heartbeat',
|
||||
'Batch Market Instructions': 'Batch',
|
||||
};
|
||||
|
||||
@@ -172,7 +176,7 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
return (
|
||||
<div
|
||||
data-testid="tx-type"
|
||||
className={`text-sm rounded-md leading-none px-2 py-2 inline-block ${colours}`}
|
||||
className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
@@ -83,21 +84,22 @@ describe('Txs infinite list item', () => {
|
||||
|
||||
it('renders data correctly', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
|
||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { TruncatedLink } from '../truncate/truncated-link';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { TxOrderType } from './tx-order-type';
|
||||
@@ -6,8 +5,25 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
|
||||
import { toHex } from '../search/detect-search';
|
||||
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import { PartyLink } from '../links';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import type { Screen } from '@vegaprotocol/react-helpers';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const TRUNCATE_LENGTH = 10;
|
||||
const DEFAULT_TRUNCATE_LENGTH = 7;
|
||||
|
||||
export function getIdTruncateLength(screen: Screen): number {
|
||||
if (['xxxl', 'xxl'].includes(screen)) {
|
||||
return 64;
|
||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||
return 32;
|
||||
}
|
||||
return DEFAULT_TRUNCATE_LENGTH;
|
||||
}
|
||||
|
||||
export function shouldTruncateParty(screen: Screen): boolean {
|
||||
return !['xxxl', 'xxl', 'xl'].includes(screen);
|
||||
}
|
||||
|
||||
export const TxsInfiniteListItem = ({
|
||||
hash,
|
||||
@@ -17,6 +33,12 @@ export const TxsInfiniteListItem = ({
|
||||
block,
|
||||
command,
|
||||
}: Partial<BlockExplorerTransactionResult>) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const idTruncateLength = useMemo(
|
||||
() => getIdTruncateLength(screenSize),
|
||||
[screenSize]
|
||||
);
|
||||
|
||||
if (
|
||||
!hash ||
|
||||
!submitter ||
|
||||
@@ -29,68 +51,40 @@ export const TxsInfiniteListItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<tr
|
||||
data-testid="transaction-row"
|
||||
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10"
|
||||
className="transaction-row text-left items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item py-[2px]"
|
||||
>
|
||||
<div
|
||||
className="text-sm col-span-10 md:col-span-3 leading-none"
|
||||
<td
|
||||
className="text-sm leading-none whitespace-nowrap font-mono"
|
||||
data-testid="tx-hash"
|
||||
>
|
||||
<span className="md:hidden uppercase text-vega-dark-300">
|
||||
ID:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${toHex(hash)}`}
|
||||
text={hash}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-10 md:col-span-3 leading-none"
|
||||
data-testid="pub-key"
|
||||
>
|
||||
<span className="md:hidden uppercase text-vega-dark-300">
|
||||
By:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm col-span-5 md:col-span-2 leading-none flex items-center">
|
||||
<TxOrderType orderType={type} command={command} />
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-3 md:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-block"
|
||||
>
|
||||
<span className="md:hidden uppercase text-vega-dark-300">
|
||||
Block:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.BLOCKS}/${block}`}
|
||||
text={block}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-2 md:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-success"
|
||||
>
|
||||
<span className="md:hidden uppercase text-vega-dark-300">
|
||||
Success
|
||||
</span>
|
||||
{isNumber(code) ? (
|
||||
<ChainResponseCode code={code} hideLabel={true} />
|
||||
<ChainResponseCode code={code} hideLabel={true} hideIfOk={true} />
|
||||
) : (
|
||||
code
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${toHex(hash)}`}
|
||||
text={hash}
|
||||
startChars={idTruncateLength}
|
||||
endChars={0}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-sm leading-none">
|
||||
<TxOrderType orderType={type} command={command} />
|
||||
</td>
|
||||
<td className="text-sm leading-none" data-testid="pub-key">
|
||||
<PartyLink truncate={shouldTruncateParty(screenSize)} id={submitter} />
|
||||
</td>
|
||||
<td className="text-sm items-center font-mono" data-testid="tx-block">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.BLOCKS}/${block}`}
|
||||
text={block}
|
||||
startChars={5}
|
||||
endChars={5}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TxsInfiniteList } from './txs-infinite-list';
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
return Array.from(Array(number)).map((_) => ({
|
||||
@@ -40,7 +41,7 @@ describe('Txs infinite list', () => {
|
||||
it('should display a "no items" message when no items provided', () => {
|
||||
render(
|
||||
<TxsInfiniteList
|
||||
txs={undefined}
|
||||
txs={undefined as unknown as BlockExplorerTransactionResult[]}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={false}
|
||||
loadMoreTxs={() => null}
|
||||
@@ -48,23 +49,7 @@ describe('Txs infinite list', () => {
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('emptylist')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('This chain has 0 transactions')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('error is displayed at item level', () => {
|
||||
const txs = generateTxs(1);
|
||||
render(
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={false}
|
||||
loadMoreTxs={() => null}
|
||||
error={Error('test error!')}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('Cannot fetch transaction')).toBeInTheDocument();
|
||||
expect(screen.getByText('No transactions found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('item renders data of n length into list of n length', () => {
|
||||
@@ -73,85 +58,22 @@ describe('Txs infinite list', () => {
|
||||
const txs = generateTxs(7);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={false}
|
||||
loadMoreTxs={() => null}
|
||||
error={undefined}
|
||||
/>
|
||||
<MockedProvider>
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={false}
|
||||
loadMoreTxs={() => null}
|
||||
error={undefined}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('infinite-scroll-wrapper')
|
||||
.querySelectorAll('.txs-infinite-list-item')
|
||||
.getByTestId('transactions-list')
|
||||
.querySelectorAll('.transaction-row')
|
||||
).toHaveLength(7);
|
||||
});
|
||||
|
||||
it('tries to load more items when required to initially fill the list', () => {
|
||||
// For example, if initially rendering 15, the bottom of the list is
|
||||
// in view of the viewport, and the callback should be executed
|
||||
const txs = generateTxs(15);
|
||||
const callback = jest.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={true}
|
||||
loadMoreTxs={callback}
|
||||
error={undefined}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(callback.mock.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not try to load more items if there are no more', () => {
|
||||
const txs = generateTxs(3);
|
||||
const callback = jest.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={false}
|
||||
loadMoreTxs={callback}
|
||||
error={undefined}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(callback.mock.calls.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('loads more items is called when scrolled', () => {
|
||||
const txs = generateTxs(14);
|
||||
const callback = jest.fn();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteList
|
||||
txs={txs}
|
||||
areTxsLoading={false}
|
||||
hasMoreTxs={true}
|
||||
loadMoreTxs={callback}
|
||||
error={undefined}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
fireEvent.scroll(screen.getByTestId('infinite-scroll-wrapper'), {
|
||||
target: { scrollY: 2000 },
|
||||
});
|
||||
});
|
||||
|
||||
expect(callback.mock.calls.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
@@ -11,82 +7,46 @@ import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
interface TxsInfiniteListProps {
|
||||
hasMoreTxs: boolean;
|
||||
areTxsLoading: boolean | undefined;
|
||||
txs: BlockExplorerTransactionResult[] | undefined;
|
||||
txs: BlockExplorerTransactionResult[];
|
||||
loadMoreTxs: () => void;
|
||||
error: Error | undefined;
|
||||
className?: string;
|
||||
hasFilters?: boolean;
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
index: BlockExplorerTransactionResult;
|
||||
style: React.CSSProperties;
|
||||
isLoading: boolean;
|
||||
error: Error | undefined;
|
||||
tx: BlockExplorerTransactionResult;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const NOOP = () => {};
|
||||
|
||||
const Item = ({ index, style, isLoading, error }: ItemProps) => {
|
||||
let content;
|
||||
if (error) {
|
||||
content = t(`Cannot fetch transaction`);
|
||||
} else if (isLoading) {
|
||||
content = <Loader />;
|
||||
} else {
|
||||
const {
|
||||
hash,
|
||||
submitter,
|
||||
type,
|
||||
command,
|
||||
block,
|
||||
code,
|
||||
index: blockIndex,
|
||||
} = index;
|
||||
content = (
|
||||
<TxsInfiniteListItem
|
||||
type={type}
|
||||
code={code}
|
||||
command={command}
|
||||
submitter={submitter}
|
||||
hash={hash}
|
||||
block={block}
|
||||
index={blockIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <div style={style}>{content}</div>;
|
||||
const Item = ({ tx }: ItemProps) => {
|
||||
const { hash, submitter, type, command, block, code, index: blockIndex } = tx;
|
||||
return (
|
||||
<TxsInfiniteListItem
|
||||
type={type}
|
||||
code={code}
|
||||
command={command}
|
||||
submitter={submitter}
|
||||
hash={hash}
|
||||
block={block}
|
||||
index={blockIndex}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const TxsInfiniteList = ({
|
||||
hasMoreTxs,
|
||||
areTxsLoading,
|
||||
txs,
|
||||
loadMoreTxs,
|
||||
error,
|
||||
className,
|
||||
hasFilters = false,
|
||||
}: TxsInfiniteListProps) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const isStacked = ['xs', 'sm'].includes(screenSize);
|
||||
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
|
||||
const hasMountedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMountedRef.current) {
|
||||
if (infiniteLoaderRef.current) {
|
||||
infiniteLoaderRef.current.resetloadMoreItemsCache(true);
|
||||
}
|
||||
}
|
||||
hasMountedRef.current = true;
|
||||
}, [loadMoreTxs]);
|
||||
|
||||
if (!txs) {
|
||||
if (!txs || txs.length === 0) {
|
||||
if (!areTxsLoading) {
|
||||
return (
|
||||
<EmptyList
|
||||
heading={t('This chain has 0 transactions')}
|
||||
label={t('Check back soon')}
|
||||
heading={t('No transactions found')}
|
||||
label={
|
||||
hasFilters ? t('Try a different filter') : t('Check back soon')
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -94,57 +54,26 @@ export const TxsInfiniteList = ({
|
||||
}
|
||||
}
|
||||
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
const itemCount = hasMoreTxs ? txs.length + 1 : txs.length;
|
||||
|
||||
// Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const loadMoreItems = areTxsLoading ? NOOP : loadMoreTxs;
|
||||
|
||||
// Every row is loaded except for our loading indicator row.
|
||||
const isItemLoaded = (index: number) => !hasMoreTxs || index < txs.length;
|
||||
|
||||
return (
|
||||
<div className={className} data-testid="transactions-list">
|
||||
<div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
|
||||
<div className="col-span-3">
|
||||
<span className="hidden xl:inline">{t('Transaction')} </span>
|
||||
<span>ID</span>
|
||||
</div>
|
||||
<div className="col-span-3">{t('Submitted By')}</div>
|
||||
<div className="col-span-2">{t('Type')}</div>
|
||||
<div className="col-span-1">{t('Block')}</div>
|
||||
<div className="col-span-1">{t('Success')}</div>
|
||||
</div>
|
||||
<div data-testid="infinite-scroll-wrapper">
|
||||
<InfiniteLoader
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
ref={infiniteLoaderRef}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<List
|
||||
className="List"
|
||||
height={995}
|
||||
itemCount={itemCount}
|
||||
itemSize={isStacked ? 134 : 50}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={'100%'}
|
||||
>
|
||||
{({ index, style }) => (
|
||||
<Item
|
||||
index={txs[index]}
|
||||
style={style}
|
||||
isLoading={!isItemLoaded(index)}
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
</div>
|
||||
<div className="overflow-scroll">
|
||||
<table className={className} data-testid="transactions-list">
|
||||
<thead>
|
||||
<tr className="w-full mb-3 text-vega-dark-300 uppercase text-left">
|
||||
<th>
|
||||
<span className="hidden xl:inline">{t('Txn')} </span>
|
||||
<span>ID</span>
|
||||
</th>
|
||||
<th>{t('Type')}</th>
|
||||
<th className="text-left">{t('From')}</th>
|
||||
<th>{t('Block')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{txs.map((t) => (
|
||||
<Item key={t.hash} tx={t} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { TruncatedLink } from '../truncate/truncated-link';
|
||||
import { TxOrderType } from './tx-order-type';
|
||||
import { Table, TableRow, TableCell } from '../table';
|
||||
import { Table, TableRow } from '../table';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
|
||||
import { getTxsDataUrl } from '../../hooks/use-txs-data';
|
||||
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
|
||||
interface TxsPerBlockProps {
|
||||
blockHeight: string;
|
||||
txCount: number;
|
||||
}
|
||||
|
||||
const truncateLength = 5;
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
const filters = `filters[block.height]=${blockHeight}`;
|
||||
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
|
||||
@@ -33,53 +27,23 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
<thead>
|
||||
<TableRow modifier="bordered" className="font-mono">
|
||||
<td>{t('Transaction')}</td>
|
||||
<td>{t('From')}</td>
|
||||
<td>{t('Type')}</td>
|
||||
<td>{t('Status')}</td>
|
||||
<td>{t('From')}</td>
|
||||
<td>{t('Block')}</td>
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.transactions.map(
|
||||
({ hash, submitter, type, command, code }) => {
|
||||
({ hash, submitter, type, command, code, block }) => {
|
||||
return (
|
||||
<TableRow
|
||||
modifier="bordered"
|
||||
key={hash}
|
||||
data-testid="transaction-row"
|
||||
>
|
||||
<TableCell
|
||||
modifier="bordered"
|
||||
className="pr-12 font-mono"
|
||||
>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${hash}`}
|
||||
text={hash}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
modifier="bordered"
|
||||
className="pr-12 font-mono"
|
||||
>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<TxOrderType orderType={type} command={command} />
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered" className="text">
|
||||
{isNumber(code) ? (
|
||||
<ChainResponseCode code={code} hideLabel={true} />
|
||||
) : (
|
||||
code
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TxsInfiniteListItem
|
||||
block={block}
|
||||
hash={hash}
|
||||
submitter={submitter}
|
||||
type={type}
|
||||
command={command}
|
||||
code={code}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
|
||||
@@ -56,10 +56,14 @@ export function VoteIcon({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`}
|
||||
className={`voteicon inline-block py-0 px-2 py rounded-md text-white whitespace-nowrap leading-tight sm align-top ${bg}`}
|
||||
>
|
||||
<Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} />
|
||||
<span className={`text-base text-${text}`} data-testid="label">
|
||||
<Icon
|
||||
name={icon}
|
||||
size={3}
|
||||
className={`mr-2 p-0 mb-[-1px] fill-${fill}`}
|
||||
/>
|
||||
<span className={`text-${text}`} data-testid="label">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -10,16 +10,18 @@ import isNumber from 'lodash/isNumber';
|
||||
export interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
hasMoreTxs: boolean;
|
||||
lastCursor: string;
|
||||
cursor: string;
|
||||
previousCursors: string[];
|
||||
hasPreviousPage: boolean;
|
||||
}
|
||||
|
||||
export interface IUseTxsData {
|
||||
limit?: number;
|
||||
limit: number;
|
||||
filters?: string;
|
||||
}
|
||||
|
||||
interface IGetTxsDataUrl {
|
||||
limit?: string;
|
||||
limit: string;
|
||||
filters?: string;
|
||||
}
|
||||
|
||||
@@ -40,63 +42,89 @@ export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
|
||||
useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: true,
|
||||
lastCursor: '',
|
||||
});
|
||||
const [
|
||||
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
|
||||
setTxsState,
|
||||
] = useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: false,
|
||||
previousCursors: [],
|
||||
cursor: '',
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
|
||||
const url = getTxsDataUrl({ limit: limit?.toString(), filters });
|
||||
const url = getTxsDataUrl({ limit: limit.toString(), filters });
|
||||
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, false);
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, true);
|
||||
|
||||
useEffect(() => {
|
||||
if (data && isNumber(data?.transactions?.length)) {
|
||||
setTxsState((prev) => ({
|
||||
txsData: [...prev.txsData, ...data.transactions],
|
||||
hasMoreTxs: data.transactions.length > 0,
|
||||
lastCursor:
|
||||
data.transactions[data.transactions.length - 1]?.cursor || '',
|
||||
}));
|
||||
if (!loading && data && isNumber(data.transactions.length)) {
|
||||
setTxsState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
txsData: data.transactions,
|
||||
hasMoreTxs: data.transactions.length >= limit,
|
||||
cursor: data?.transactions.at(-1)?.cursor || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [setTxsState, data]);
|
||||
}, [loading, setTxsState, data, limit]);
|
||||
|
||||
useEffect(() => {
|
||||
setTxsState((prev) => ({
|
||||
txsData: [],
|
||||
hasMoreTxs: true,
|
||||
lastCursor: '',
|
||||
}));
|
||||
}, [filters]);
|
||||
const nextPage = useCallback(() => {
|
||||
const c = data?.transactions.at(0)?.cursor;
|
||||
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
|
||||
|
||||
const loadTxs = useCallback(() => {
|
||||
return refetch({
|
||||
limit: limit,
|
||||
before: lastCursor,
|
||||
});
|
||||
}, [lastCursor, limit, refetch]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
lastCursor: '',
|
||||
hasMoreTxs: true,
|
||||
txsData: [],
|
||||
hasPreviousPage: true,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
}, [setTxsState]);
|
||||
|
||||
return refetch({
|
||||
limit,
|
||||
before: cursor,
|
||||
});
|
||||
}, [data, previousCursors, cursor, limit, refetch]);
|
||||
|
||||
const previousPage = useCallback(() => {
|
||||
const previousCursor = [...previousCursors].pop();
|
||||
const newPreviousCursors = previousCursors.slice(0, -1);
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
hasPreviousPage: newPreviousCursors.length > 0,
|
||||
previousCursors: newPreviousCursors,
|
||||
}));
|
||||
return refetch({
|
||||
limit,
|
||||
before: previousCursor,
|
||||
});
|
||||
}, [previousCursors, limit, refetch]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState(() => ({
|
||||
txsData: [],
|
||||
cursor: '',
|
||||
previousCursors: [],
|
||||
hasMoreTxs: false,
|
||||
hasPreviousPage: false,
|
||||
}));
|
||||
|
||||
refetch({ limit });
|
||||
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return {
|
||||
data,
|
||||
txsData,
|
||||
loading,
|
||||
error,
|
||||
txsData,
|
||||
hasMoreTxs,
|
||||
lastCursor,
|
||||
hasPreviousPage,
|
||||
previousCursors,
|
||||
cursor,
|
||||
refreshTxs,
|
||||
loadTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { Footer } from '../components/footer/footer';
|
||||
import { Header } from '../components/header';
|
||||
import { Routes } from './route-names';
|
||||
import { useExplorerNodeNamesLazyQuery } from './validators/__generated__/NodeNames';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -39,6 +40,7 @@ export const Layout = () => {
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
const fixedWidthClasses = 'w-full max-w-[1500px] mx-auto';
|
||||
useExplorerNodeNamesLazyQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -49,7 +51,7 @@ export const Layout = () => {
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
'relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -2,12 +2,15 @@ import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { SourceType } from './oracle';
|
||||
import { OracleSigners } from './oracle-signers';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
function renderComponent(sourceType: SourceType) {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<OracleSigners sourceType={sourceType} />
|
||||
</MemoryRouter>
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<OracleSigners sourceType={sourceType} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
@@ -14,8 +14,11 @@ import { PartyBlockStake } from './components/party-block-stake';
|
||||
import { PartyBlockAccounts } from './components/party-block-accounts';
|
||||
import { isValidPartyId } from './components/party-id-error';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
|
||||
const Party = () => {
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
const { party } = useParams<{ party: string }>();
|
||||
|
||||
useDocumentTitle(['Public keys', party || '-']);
|
||||
@@ -24,10 +27,24 @@ const Party = () => {
|
||||
const partyId = toNonHex(party ? party : '');
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
||||
const filters = `filters[tx.submitter]=${partyId}`;
|
||||
const { hasMoreTxs, loadTxs, error, txsData, loading } = useTxsData({
|
||||
limit: 10,
|
||||
filters,
|
||||
const baseFilters = `filters[tx.submitter]=${partyId}`;
|
||||
const f =
|
||||
filters && filters.size === 1
|
||||
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: baseFilters;
|
||||
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
} = useTxsData({
|
||||
limit: 25,
|
||||
filters: f,
|
||||
});
|
||||
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
@@ -81,14 +98,24 @@ const Party = () => {
|
||||
</div>
|
||||
|
||||
<SubHeading>{t('Transactions')}</SubHeading>
|
||||
<TxsListNavigation
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
</TxsListNavigation>
|
||||
{!error && txsData ? (
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
loadMoreTxs={nextPage}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
className="mb-28 w-full"
|
||||
/>
|
||||
) : (
|
||||
<Splash>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { BlocksRefetch } from '../../../components/blocks';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||
|
||||
const BE_TXS_PER_REQUEST = 15;
|
||||
const BE_TXS_PER_REQUEST = 25;
|
||||
|
||||
export const TxsList = () => {
|
||||
useDocumentTitle(['Transactions']);
|
||||
@@ -21,6 +21,11 @@ export const TxsList = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a list of transactions with filters and controls to navigate through the list.
|
||||
*
|
||||
* @returns {JSX.Element} Transaction List and controls
|
||||
*/
|
||||
export const TxsListFiltered = () => {
|
||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||
|
||||
@@ -29,26 +34,40 @@ export const TxsListFiltered = () => {
|
||||
? `filters[cmd.type]=${Array.from(filters)[0]}`
|
||||
: '';
|
||||
|
||||
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } =
|
||||
useTxsData({
|
||||
limit: BE_TXS_PER_REQUEST,
|
||||
filters: f,
|
||||
});
|
||||
const {
|
||||
hasMoreTxs,
|
||||
nextPage,
|
||||
previousPage,
|
||||
error,
|
||||
refreshTxs,
|
||||
loading,
|
||||
txsData,
|
||||
hasPreviousPage,
|
||||
} = useTxsData({
|
||||
limit: BE_TXS_PER_REQUEST,
|
||||
filters: f,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<menu className="mb-2">
|
||||
<BlocksRefetch refetch={refreshTxs} />
|
||||
<TxsListNavigation
|
||||
refreshTxs={refreshTxs}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
loading={loading}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
>
|
||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
||||
</menu>
|
||||
|
||||
</TxsListNavigation>
|
||||
<TxsInfiniteList
|
||||
hasFilters={filters.size > 0}
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
loadMoreTxs={nextPage}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
className="mb-28 w-full min-w-[400px]"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { TxDetails } from './tx-details';
|
||||
import type {
|
||||
BlockExplorerTransactionResult,
|
||||
ValidatorHeartbeat,
|
||||
} from '../../../routes/types/block-explorer-response';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
// Note: Long enough that there is a truncated output and a full output
|
||||
const pubKey =
|
||||
@@ -27,9 +28,11 @@ const txData: BlockExplorerTransactionResult = {
|
||||
};
|
||||
|
||||
const renderComponent = (txData: BlockExplorerTransactionResult) => (
|
||||
<Router>
|
||||
<TxDetails txData={txData} pubKey={pubKey} />
|
||||
</Router>
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<TxDetails txData={txData} pubKey={pubKey} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('Transaction details', () => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
query ExplorerNodeNames {
|
||||
nodesConnection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
pubkey
|
||||
tmPubkey
|
||||
ethereumAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerNodeNamesQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerNodeNamesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, tmPubkey: string, ethereumAddress: string } } | null> | null } };
|
||||
|
||||
|
||||
export const ExplorerNodeNamesDocument = gql`
|
||||
query ExplorerNodeNames {
|
||||
nodesConnection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
pubkey
|
||||
tmPubkey
|
||||
ethereumAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerNodeNamesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerNodeNamesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerNodeNamesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerNodeNamesQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerNodeNamesQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>(ExplorerNodeNamesDocument, options);
|
||||
}
|
||||
export function useExplorerNodeNamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>(ExplorerNodeNamesDocument, options);
|
||||
}
|
||||
export type ExplorerNodeNamesQueryHookResult = ReturnType<typeof useExplorerNodeNamesQuery>;
|
||||
export type ExplorerNodeNamesLazyQueryHookResult = ReturnType<typeof useExplorerNodeNamesLazyQuery>;
|
||||
export type ExplorerNodeNamesQueryResult = Apollo.QueryResult<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>;
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@tailwind base;
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -836,5 +836,6 @@
|
||||
"AllProposals": "All proposals",
|
||||
"RejectedProposals": "Rejected proposals",
|
||||
"networkGovernance": "Network governance",
|
||||
"networkUpgrades": "Network upgrades"
|
||||
"networkUpgrades": "Network upgrades",
|
||||
"assetSpecification": "Asset specification"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './proposal-asset-details';
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import { AssetDetail, AssetDetailsTable } from '@vegaprotocol/assets';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
|
||||
export const ProposalAssetDetails = ({
|
||||
asset,
|
||||
}: {
|
||||
asset: AssetFieldsFragment;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showAssetDetails, setShowAssetDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-asset-details">
|
||||
<CollapsibleToggle
|
||||
toggleState={showAssetDetails}
|
||||
setToggleState={setShowAssetDetails}
|
||||
dataTestId={'proposal-asset-details-toggle'}
|
||||
>
|
||||
<SubHeading title={t('assetSpecification')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showAssetDetails && (
|
||||
<div className="mb-10 pb-4">
|
||||
<AssetDetailsTable
|
||||
asset={asset}
|
||||
omitRows={[
|
||||
AssetDetail.STATUS,
|
||||
AssetDetail.INFRASTRUCTURE_FEE_ACCOUNT_BALANCE,
|
||||
AssetDetail.GLOBAL_REWARD_POOL_ACCOUNT_BALANCE,
|
||||
AssetDetail.MAKER_PAID_FEES_ACCOUNT_BALANCE,
|
||||
AssetDetail.MAKER_RECEIVED_FEES_ACCOUNT_BALANCE,
|
||||
AssetDetail.LP_FEE_REWARD_ACCOUNT_BALANCE,
|
||||
AssetDetail.MARKET_PROPOSER_REWARD_ACCOUNT_BALANCE,
|
||||
]}
|
||||
inline={true}
|
||||
noBorder={true}
|
||||
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
|
||||
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||
import { VoteDetails } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
@@ -17,6 +18,8 @@ import { ProposalMarketData } from '../proposal-market-data';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import type { AssetQuery } from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
|
||||
export enum ProposalType {
|
||||
@@ -30,6 +33,7 @@ export enum ProposalType {
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
newMarketData?: MarketInfoWithData | null;
|
||||
assetData?: AssetQuery | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: any;
|
||||
}
|
||||
@@ -38,6 +42,7 @@ export const Proposal = ({
|
||||
proposal,
|
||||
restData,
|
||||
newMarketData,
|
||||
assetData,
|
||||
}: ProposalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { params, loading, error } = useNetworkParams([
|
||||
@@ -54,6 +59,23 @@ export const Proposal = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
let asset = assetData
|
||||
? removePaginationWrapper(assetData.assetsConnection?.edges)[0]
|
||||
: undefined;
|
||||
|
||||
if (proposal.terms.change.__typename === 'UpdateAsset' && asset) {
|
||||
asset = {
|
||||
...asset,
|
||||
quantum: proposal.terms.change.quantum,
|
||||
};
|
||||
|
||||
if (asset.source.__typename === 'ERC20') {
|
||||
asset.source.lifetimeLimit = proposal.terms.change.source.lifetimeLimit;
|
||||
asset.source.withdrawThreshold =
|
||||
proposal.terms.change.source.withdrawThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
let minVoterBalance = null;
|
||||
let proposalType = null;
|
||||
|
||||
@@ -138,6 +160,14 @@ export const Proposal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(proposal.terms.change.__typename === 'NewAsset' ||
|
||||
proposal.terms.change.__typename === 'UpdateAsset') &&
|
||||
asset && (
|
||||
<div className="mb-4">
|
||||
<ProposalAssetDetails asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
</div>
|
||||
|
||||
@@ -191,12 +191,13 @@ export const ProposalsList = ({
|
||||
{sortedProposals.open.length > 0 ||
|
||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||
<ul data-testid="open-proposals">
|
||||
{sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
{filterString.length < 1 &&
|
||||
sortedProtocolUpgradeProposals.open.map((proposal) => (
|
||||
<ProtocolUpgradeProposalsListItem
|
||||
key={proposal.upgradeBlockHeight}
|
||||
proposal={proposal}
|
||||
/>
|
||||
))}
|
||||
|
||||
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
|
||||
<ProposalsListItem key={proposal?.id} proposal={proposal} />
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { ENV } from '../../../config';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useAssetQuery } from '@vegaprotocol/assets';
|
||||
|
||||
export const ProposalContainer = () => {
|
||||
const params = useParams<{ proposalId: string }>();
|
||||
@@ -35,6 +36,25 @@ export const ProposalContainer = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: assetData,
|
||||
loading: assetLoading,
|
||||
error: assetError,
|
||||
} = useAssetQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
assetId:
|
||||
(data?.proposal?.terms.change.__typename === 'NewAsset' &&
|
||||
data?.proposal?.id) ||
|
||||
(data?.proposal?.terms.change.__typename === 'UpdateAsset' &&
|
||||
data.proposal.terms.change.assetId) ||
|
||||
'',
|
||||
},
|
||||
skip: !['NewAsset', 'UpdateAsset'].includes(
|
||||
data?.proposal?.terms?.change?.__typename || ''
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(refetch, 2000);
|
||||
return () => clearInterval(interval);
|
||||
@@ -42,15 +62,20 @@ export const ProposalContainer = () => {
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading || newMarketLoading}
|
||||
error={error || newMarketError}
|
||||
data={newMarketData ? { newMarketData, data } : data}
|
||||
loading={loading || newMarketLoading || assetLoading}
|
||||
error={error || newMarketError || assetError}
|
||||
data={{
|
||||
...data,
|
||||
...(newMarketData ? { newMarketData } : {}),
|
||||
...(assetData ? { assetData } : {}),
|
||||
}}
|
||||
>
|
||||
{data?.proposal ? (
|
||||
<Proposal
|
||||
proposal={data.proposal}
|
||||
restData={restData}
|
||||
newMarketData={newMarketData}
|
||||
assetData={assetData}
|
||||
/>
|
||||
) : (
|
||||
<ProposalNotFound />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
+238
-254
@@ -21,11 +21,14 @@ import {
|
||||
HealthBar,
|
||||
TooltipCellComponent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { GetRowIdParams, RowClickedEvent } from 'ag-grid-community';
|
||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type {
|
||||
GetRowIdParams,
|
||||
RowClickedEvent,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
|
||||
import { Grid } from '../../grid';
|
||||
import { HealthDialog } from '../../health-dialog';
|
||||
@@ -39,6 +42,234 @@ export const MarketList = () => {
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market (futures)'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
cellRenderer: ({ value, data }: { value: string; data: Market }) => {
|
||||
return (
|
||||
<>
|
||||
<span className="leading-3">{value}</span>
|
||||
<span className="leading-3">
|
||||
{
|
||||
data?.tradableInstrument?.instrument?.product?.settlementAsset
|
||||
?.symbol
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
minWidth: 100,
|
||||
flex: 1,
|
||||
headerTooltip: t('The market name and settlement asset'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Market Code'),
|
||||
headerTooltip: t(
|
||||
'The market code is a unique identifier for this market'
|
||||
),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Type'),
|
||||
headerTooltip: t('Type'),
|
||||
field: 'tradableInstrument.instrument.product.__typename',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Last Price'),
|
||||
headerTooltip: t('Latest price for this market'),
|
||||
field: 'data.markPrice',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
|
||||
value && data
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Change (24h)'),
|
||||
headerTooltip: t('Change in price over the last 24h'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
|
||||
if (data && data.candles) {
|
||||
const prices = data.candles.map((candle) => candle.close);
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={prices}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
);
|
||||
} else return <div>{t('-')}</div>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Volume (24h)'),
|
||||
field: 'dayVolume',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
|
||||
value && data
|
||||
? `${addDecimalsFormatNumber(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
)} (${displayChange(data.volumeChange)})`
|
||||
: '-',
|
||||
headerTooltip: t('The trade volume over the last 24h'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Total staked by LPs'),
|
||||
field: 'liquidityCommitted',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value.toString(),
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Target stake'),
|
||||
field: 'target',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'target'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
headerTooltip: t(
|
||||
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('% Target stake met'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
if (data) {
|
||||
const roundedPercentage =
|
||||
parseInt(
|
||||
(data.liquidityCommitted / parseFloat(data.target)).toFixed(0)
|
||||
) * 100;
|
||||
const display = Number.isNaN(roundedPercentage)
|
||||
? 'N/A'
|
||||
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
|
||||
return display;
|
||||
} else return '-';
|
||||
},
|
||||
headerTooltip: t('% Target stake met'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Fee levels'),
|
||||
field: 'fees',
|
||||
valueFormatter: ({ value }: VegaValueFormatterParams<Market, 'fees'>) =>
|
||||
value ? `${value.factors.liquidityFee}%` : '-',
|
||||
headerTooltip: t('Fee level for this market'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => {
|
||||
return <Status trigger={data.data?.trigger} tradingMode={value} />;
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The current market status - those below the target stake mark are most in need of liquidity'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerComponent: () => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => (
|
||||
<HealthBar
|
||||
target={data.target}
|
||||
decimals={
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
levels={data.feeLevels}
|
||||
intent={intentForStatus(value)}
|
||||
/>
|
||||
),
|
||||
sortable: false,
|
||||
cellStyle: { overflow: 'unset' },
|
||||
},
|
||||
{
|
||||
headerName: t('Age'),
|
||||
field: 'marketTimestamps.open',
|
||||
headerTooltip: t('Age of the market'),
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
|
||||
return value ? formatDistanceToNow(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Closing Time'),
|
||||
field: 'tradableInstrument.instrument.metadata.tags',
|
||||
headerTooltip: t('Closing time of the market'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
@@ -64,258 +295,11 @@ export const MarketList = () => {
|
||||
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
isRowClickable
|
||||
tooltipShowDelay={500}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market (futures)')}
|
||||
field="tradableInstrument.instrument.name"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: string;
|
||||
data: Market;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<span className="leading-3">{value}</span>
|
||||
<span className="leading-3">
|
||||
{
|
||||
data?.tradableInstrument?.instrument?.product
|
||||
?.settlementAsset?.symbol
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
flex="1"
|
||||
headerTooltip={t('The market name and settlement asset')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Market Code')}
|
||||
headerTooltip={t(
|
||||
'The market code is a unique identifier for this market'
|
||||
)}
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Type')}
|
||||
headerTooltip={t('Type')}
|
||||
field="tradableInstrument.instrument.product.__typename"
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Last Price')}
|
||||
headerTooltip={t('Latest price for this market')}
|
||||
field="data.markPrice"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
|
||||
value && data
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Change (24h)')}
|
||||
headerTooltip={t('Change in price over the last 24h')}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
|
||||
if (data && data.candles) {
|
||||
const prices = data.candles.map((candle) => candle.close);
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={prices}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
);
|
||||
} else return <div>{t('-')}</div>;
|
||||
}}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Volume (24h)')}
|
||||
field="dayVolume"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
|
||||
value && data
|
||||
? `${addDecimalsFormatNumber(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
)} (${displayChange(data.volumeChange)})`
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t('The trade volume over the last 24h')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Total staked by LPs')}
|
||||
field="liquidityCommitted"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value.toString(),
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t(
|
||||
'The amount of funds allocated to provide liquidity'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Target stake')}
|
||||
field="target"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'target'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t(
|
||||
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('% Target stake met')}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, ''>) => {
|
||||
if (data) {
|
||||
const roundedPercentage =
|
||||
parseInt(
|
||||
(data.liquidityCommitted / parseFloat(data.target)).toFixed(
|
||||
0
|
||||
)
|
||||
) * 100;
|
||||
const display = Number.isNaN(roundedPercentage)
|
||||
? 'N/A'
|
||||
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
|
||||
return display;
|
||||
} else return '-';
|
||||
}}
|
||||
headerTooltip={t('% Target stake met')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Fee levels')}
|
||||
field="fees"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'fees'>) =>
|
||||
value ? `${value.factors.liquidityFee}%` : '-'
|
||||
}
|
||||
headerTooltip={t('Fee level for this market')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="tradingMode"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => {
|
||||
return (
|
||||
<Status trigger={data.data?.trigger} tradingMode={value} />
|
||||
);
|
||||
}}
|
||||
headerTooltip={t(
|
||||
'The current market status - those below the target stake mark are most in need of liquidity'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerComponent={() => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
field="tradingMode"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => (
|
||||
<HealthBar
|
||||
target={data.target}
|
||||
decimals={
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
levels={data.feeLevels}
|
||||
intent={intentForStatus(value)}
|
||||
/>
|
||||
)}
|
||||
sortable={false}
|
||||
cellStyle={{ overflow: 'unset' }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Age')}
|
||||
field="marketTimestamps.open"
|
||||
headerTooltip={t('Age of the market')}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
|
||||
return value ? formatDistanceToNow(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Closing Time')}
|
||||
field="tradableInstrument.instrument.metadata.tags"
|
||||
headerTooltip={t('Closing time of the market')}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
/>
|
||||
<HealthDialog
|
||||
isOpen={isHealthDialogOpen}
|
||||
onChange={() => {
|
||||
|
||||
+73
-70
@@ -1,7 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { GetRowIdParams } from 'ag-grid-community';
|
||||
import type { GetRowIdParams, ColDef } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import type {
|
||||
@@ -36,6 +35,75 @@ export const LPProvidersGrid = ({
|
||||
};
|
||||
}) => {
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('LPs'),
|
||||
field: 'party.id',
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
headerTooltip: t('Liquidity providers'),
|
||||
},
|
||||
{
|
||||
headerName: t('Duration'),
|
||||
valueFormatter: formatToHours,
|
||||
field: 'createdAt',
|
||||
headerTooltip: t('Time in market'),
|
||||
},
|
||||
{
|
||||
headerName: t('Equity-like share'),
|
||||
field: 'equityLikeShare',
|
||||
valueFormatter: ({ value }: { value?: string | null }) => {
|
||||
return value
|
||||
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
|
||||
: '';
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
|
||||
),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('committed bond'),
|
||||
field: 'commitmentAmount',
|
||||
valueFormatter: ({ value }: { value?: string | null }) =>
|
||||
value ? formatWithAsset(value, settlementAsset) : '0',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('Margin Req.'),
|
||||
field: 'margin',
|
||||
headerTooltip: t(
|
||||
'Margin required for arising positions based on liquidity commitment'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('24h Fees'),
|
||||
field: 'fees',
|
||||
headerTooltip: t(
|
||||
'Total fees earned by the liquidity provider in the last 24 hours'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Fee level'),
|
||||
valueFormatter: ({ value }: { value?: string | null }) => `${value}%`,
|
||||
field: 'fee',
|
||||
headerTooltip: t(
|
||||
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('APY'),
|
||||
field: 'apy',
|
||||
headerTooltip: t(
|
||||
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
|
||||
),
|
||||
},
|
||||
],
|
||||
[settlementAsset]
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
@@ -49,74 +117,9 @@ export const LPProvidersGrid = ({
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
minWidth: 100,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
rowHeight={92}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('LPs')}
|
||||
field="party.id"
|
||||
flex="1"
|
||||
minWidth={100}
|
||||
headerTooltip={t('Liquidity providers')}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Duration')}
|
||||
valueFormatter={formatToHours}
|
||||
field="createdAt"
|
||||
headerTooltip={t('Time in market')}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Equity-like share')}
|
||||
field="equityLikeShare"
|
||||
valueFormatter={({ value }: { value?: string | null }) => {
|
||||
return value
|
||||
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
|
||||
: '';
|
||||
}}
|
||||
headerTooltip={t(
|
||||
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
|
||||
)}
|
||||
minWidth={140}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('committed bond')}
|
||||
field="commitmentAmount"
|
||||
valueFormatter={({ value }: { value?: string | null }) =>
|
||||
value ? formatWithAsset(value, settlementAsset) : '0'
|
||||
}
|
||||
headerTooltip={t('The amount of funds allocated to provide liquidity')}
|
||||
minWidth={140}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Margin Req.')}
|
||||
field="margin"
|
||||
headerTooltip={t(
|
||||
'Margin required for arising positions based on liquidity commitment'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('24h Fees')}
|
||||
field="fees"
|
||||
headerTooltip={t(
|
||||
'Total fees earned by the liquidity provider in the last 24 hours'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Fee level')}
|
||||
valueFormatter={({ value }: { value?: string | null }) => `${value}%`}
|
||||
field="fee"
|
||||
headerTooltip={t(
|
||||
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('APY')}
|
||||
field="apy"
|
||||
headerTooltip={t(
|
||||
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import type {
|
||||
AgGridReactProps,
|
||||
@@ -7,18 +6,17 @@ import type {
|
||||
AgGridReact as AgGridReactType,
|
||||
} from 'ag-grid-react';
|
||||
import classNames from 'classnames';
|
||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
|
||||
import './grid.scss';
|
||||
|
||||
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
isRowClickable?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||
export const Grid = ({ isRowClickable, ...props }: Props) => {
|
||||
const gridRef = useRef<AgGridReactType | null>(null);
|
||||
|
||||
const resizeGrid = useCallback(() => {
|
||||
@@ -44,8 +42,6 @@ export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||
onGridReady={handleOnGridReady}
|
||||
suppressRowClickSelection
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</AgGridReact>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,28 +1,180 @@
|
||||
describe('chart', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
it('config should persist', () => {
|
||||
cy.getByTestId('Chart').click();
|
||||
cy.get('[data-testid="tab-chart"] button').as('control-buttons');
|
||||
cy.get('@control-buttons').each(($button) => {
|
||||
cy.wrap($button).click();
|
||||
cy.get(
|
||||
'[role="menuitemradio"]:first, [role="menuitemcheckbox"]:first'
|
||||
).click();
|
||||
});
|
||||
cy.getByTestId('Depth').click();
|
||||
cy.getByTestId('Chart').click();
|
||||
cy.get('@control-buttons').each(($button) => {
|
||||
cy.wrap($button).click();
|
||||
cy.get('[role="menuitemradio"]:first, [role="menuitemcheckbox"]:first')
|
||||
.within(($lastMenuItem) => {
|
||||
expect($lastMenuItem.data('state')).to.equal('checked');
|
||||
})
|
||||
.click();
|
||||
interface ItemInfoType {
|
||||
name: string;
|
||||
infoText: string;
|
||||
}
|
||||
|
||||
type CheckMenuItemsFnType = (
|
||||
triggerSelector: string,
|
||||
validTexts: string[],
|
||||
clickItem?: string
|
||||
) => void;
|
||||
type CheckMenuItemCheckboxFnType = (
|
||||
buttonText: string,
|
||||
items: ItemInfoType[]
|
||||
) => void;
|
||||
|
||||
const menuItemRadio = 'div[role="menuitemradio"]';
|
||||
const menuItemCheckbox = 'div[role="menuitemcheckbox"]';
|
||||
const button = 'button';
|
||||
const indicatorInfo = '.indicator-info-wrapper';
|
||||
|
||||
const checkMenuItems: CheckMenuItemsFnType = (
|
||||
triggerSelector,
|
||||
validTexts,
|
||||
clickItem
|
||||
) => {
|
||||
cy.get(triggerSelector).click();
|
||||
|
||||
cy.get(menuItemRadio)
|
||||
.should('have.length', validTexts.length)
|
||||
.each(($el, index) => {
|
||||
const text = $el.text().trim();
|
||||
expect(text).to.equal(validTexts[index]);
|
||||
});
|
||||
|
||||
if (clickItem) {
|
||||
cy.contains(menuItemRadio, clickItem).click();
|
||||
cy.get(triggerSelector).click();
|
||||
cy.get(`${menuItemRadio}[data-state="checked"]`)
|
||||
.invoke('text')
|
||||
.then((text: string) => {
|
||||
expect(text.trim()).to.equal(clickItem);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkMenuItemCheckbox: CheckMenuItemCheckboxFnType = (
|
||||
buttonText,
|
||||
items
|
||||
) => {
|
||||
items.forEach((item) => {
|
||||
cy.contains(button, buttonText).click();
|
||||
cy.contains(menuItemCheckbox, item.name).click();
|
||||
});
|
||||
|
||||
cy.contains(button, buttonText).click();
|
||||
cy.get(menuItemCheckbox)
|
||||
.should('have.length', items.length)
|
||||
.each(($el, index) => {
|
||||
const text = $el.text();
|
||||
expect(text).to.equal(items[index].name);
|
||||
});
|
||||
|
||||
items.forEach((item, index) => {
|
||||
cy.get(indicatorInfo)
|
||||
.eq(index + 1)
|
||||
.invoke('text')
|
||||
.should('eq', item.infoText);
|
||||
});
|
||||
|
||||
cy.contains(button, buttonText).click({ force: true });
|
||||
};
|
||||
|
||||
function getButtonSelectorByText(text: string): string {
|
||||
return `${button}[aria-haspopup="menu"]:contains(${text})`;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
describe(
|
||||
'chart display options',
|
||||
{ tags: '@smoke', testIsolation: true },
|
||||
() => {
|
||||
it('change time interval', () => {
|
||||
// 6004-CHAR-001
|
||||
checkMenuItems(
|
||||
getButtonSelectorByText('Interval:'),
|
||||
['1m', '5m', '15m', '1H', '6H', '1D'],
|
||||
'1m'
|
||||
);
|
||||
});
|
||||
|
||||
it('change display type', () => {
|
||||
// 6004-CHAR-002
|
||||
// 6004-CHAR-003
|
||||
checkMenuItems(
|
||||
'[aria-label$="chart icon"]',
|
||||
['Mountain', 'Candlestick', 'Line', 'OHLC'],
|
||||
'Mountain'
|
||||
);
|
||||
});
|
||||
|
||||
it('Overlays', () => {
|
||||
// 6004-CHAR-004
|
||||
// 6004-CHAR-008
|
||||
// 6004-CHAR-009
|
||||
// 6004-CHAR-034
|
||||
// 6004-CHAR-037
|
||||
// 6004-CHAR-039
|
||||
// 6004-CHAR-041
|
||||
|
||||
const overlayInfo: ItemInfoType[] = [
|
||||
{
|
||||
name: 'Bollinger bands',
|
||||
infoText: 'Bollinger: Upper 174.78590Lower 173.38014',
|
||||
},
|
||||
{
|
||||
name: 'Envelope',
|
||||
infoText: 'Envelope: Upper 191.29000Lower 156.51000',
|
||||
},
|
||||
{ name: 'EMA', infoText: 'EMA: 174.06793' },
|
||||
{ name: 'Moving average', infoText: 'Moving average: 174.08302' },
|
||||
{
|
||||
name: 'Price monitoring bounds',
|
||||
infoText: 'Price Monitoring Bounds: Min -Max -Reference -',
|
||||
},
|
||||
];
|
||||
|
||||
checkMenuItemCheckbox('Overlays', overlayInfo);
|
||||
});
|
||||
|
||||
it('Studies', () => {
|
||||
// 6004-CHAR-005
|
||||
// 6004-CHAR-006
|
||||
// 6004-CHAR-007
|
||||
// 6004-CHAR-042
|
||||
// 6004-CHAR-045
|
||||
// 6004-CHAR-047
|
||||
// 6004-CHAR-049
|
||||
// 6004-CHAR-051
|
||||
const studyInfo: ItemInfoType[] = [
|
||||
{
|
||||
name: 'Eldar-ray',
|
||||
infoText: 'Eldar-ray: Bull -0.08376Bear -0.58376',
|
||||
},
|
||||
{ name: 'Force index', infoText: 'Force index: 987.48858' },
|
||||
{ name: 'MACD', infoText: 'MACD: S -0.06420D 0.00359MACD -0.06062' },
|
||||
{ name: 'RSI', infoText: 'RSI: 47.08648' },
|
||||
{ name: 'Volume', infoText: 'Volume: 55,000.00000' },
|
||||
];
|
||||
cy.get(indicatorInfo).eq(1).realHover();
|
||||
cy.get('.close-button-module_closeButton__2ifkl').click({ force: true });
|
||||
cy.get(indicatorInfo).should('have.length', 1);
|
||||
|
||||
checkMenuItemCheckbox('Studies', studyInfo);
|
||||
});
|
||||
|
||||
it('price details', () => {
|
||||
// 6004-CHAR-010
|
||||
const expectedDateRegex = new RegExp(
|
||||
/^\d{2}:\d{2} \d{2} [A-Za-z]{3} \d{4}$/
|
||||
);
|
||||
const expectedOhlc = `O 173.60000H 174.00000L 173.50000C 173.90000Change −0.60000(−0.34%)`;
|
||||
cy.get(indicatorInfo)
|
||||
.eq(0)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
const actualDate = text.slice(0, -67);
|
||||
console.log(actualDate);
|
||||
const actualOhlc = text.slice(-67);
|
||||
assert.isTrue(expectedDateRegex.test(actualDate));
|
||||
assert.strictEqual(actualOhlc, expectedOhlc);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
||||
.eq(1)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderStatus}']`).should(
|
||||
'have.text',
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
|
||||
@@ -14,4 +14,4 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.18-core-0.71.8
|
||||
NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
@@ -91,10 +91,7 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
<TradingViews.fills.component onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -166,10 +163,7 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
<TradingViews.fills.component onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import classNames from 'classnames';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
@@ -12,9 +12,10 @@ import {
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
Drawer,
|
||||
DropdownMenuSeparator,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
@@ -249,14 +250,14 @@ const KeypairItem = ({ pk }: { pk: PubKey }) => {
|
||||
{truncateByChars(pk.publicKey)}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
data-testid="copy-vega-public-key"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
<Icon name="duplicate" className="mr-2" />
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
{copied && (
|
||||
@@ -278,34 +279,20 @@ const KeypairListItem = ({
|
||||
isActive: boolean;
|
||||
onSelectItem: (pk: string) => void;
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line
|
||||
let timeout: any;
|
||||
|
||||
if (copied) {
|
||||
timeout = setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 800);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [copied]);
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||
data-testid={`key-${pk.publicKey}-mobile`}
|
||||
>
|
||||
<span className="mr-2">
|
||||
<span className="flex gap-2 items-center mr-2">
|
||||
<button onClick={() => onSelectItem(pk.publicKey)}>
|
||||
<span className="uppercase">{pk.name}</span>
|
||||
</button>
|
||||
{isActive && <Icon name="tick" className="ml-2" />}
|
||||
{isActive && <VegaIcon name={VegaIconNames.TICK} />}
|
||||
</span>
|
||||
<span className="text-neutral-500 dark:text-neutral-400">
|
||||
<span className="flex gap-2 items-center">
|
||||
{truncateByChars(pk.publicKey)}{' '}
|
||||
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
@@ -313,7 +300,7 @@ const KeypairListItem = ({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
<Icon name="duplicate" className="mr-2" />
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
{copied && (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@@ -44,62 +44,79 @@ describe('AccountsTable', () => {
|
||||
});
|
||||
|
||||
it('should apply correct formatting', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const cells = await screen.findAllByRole('gridcell');
|
||||
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
const rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should apply correct formatting in view as user mode', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const cells = await screen.findAllByRole('gridcell');
|
||||
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
|
||||
expect(cells.length).toBe(expectedValues.length);
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
const rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should not add first asset as pinned', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
it('should add asset as pinned', async () => {
|
||||
const { container, rerender } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
await screen.findAllByRole('rowgroup');
|
||||
let rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(0);
|
||||
let pinnedRows = container.querySelector('.ag-floating-top-container');
|
||||
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
|
||||
|
||||
rerender(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount ?? 0).toBe(1);
|
||||
pinnedRows = container.querySelector('.ag-floating-top-container');
|
||||
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
|
||||
});
|
||||
|
||||
it('should get correct account data', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
IGetRowsParams,
|
||||
RowNode,
|
||||
IRowNode,
|
||||
RowHeightParams,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
@@ -45,8 +45,8 @@ export const percentageValue = (part: string, total: string) => {
|
||||
export const accountValuesComparator = (
|
||||
valueA: string,
|
||||
valueB: string,
|
||||
nodeA: RowNode,
|
||||
nodeB: RowNode
|
||||
nodeA: IRowNode,
|
||||
nodeB: IRowNode
|
||||
) => {
|
||||
if (isNumeric(valueA) && isNumeric(valueB)) {
|
||||
const a = toBigNum(valueA, nodeA.data.asset?.decimals);
|
||||
@@ -83,20 +83,22 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
onClickDeposit,
|
||||
onClickBreakdown,
|
||||
rowData,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const pinnedAsset = useMemo(() => {
|
||||
if (!props.pinnedAsset) {
|
||||
const pinnedRow = useMemo(() => {
|
||||
if (!pinnedAsset) {
|
||||
return;
|
||||
}
|
||||
const currentPinnedAssetRow = rowData?.find(
|
||||
(row) => row.asset.id === props.pinnedAsset?.id
|
||||
(row) => row.asset.id === pinnedAsset?.id
|
||||
);
|
||||
if (!currentPinnedAssetRow) {
|
||||
return {
|
||||
asset: props.pinnedAsset,
|
||||
asset: pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
@@ -104,7 +106,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
};
|
||||
}
|
||||
return currentPinnedAssetRow;
|
||||
}, [props.pinnedAsset, rowData]);
|
||||
}, [pinnedAsset, rowData]);
|
||||
|
||||
const { getRowHeight } = props;
|
||||
|
||||
@@ -112,17 +114,17 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === props.pinnedAsset?.id &&
|
||||
params.data.asset.id === pinnedAsset?.id &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
[props.pinnedAsset?.id, getRowHeight]
|
||||
[pinnedAsset?.id, getRowHeight]
|
||||
);
|
||||
|
||||
const showDepositButton = pinnedAsset?.balance === '0';
|
||||
const showDepositButton = pinnedRow?.balance === '0';
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
@@ -266,7 +268,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}
|
||||
return props.isReadOnly ? null : (
|
||||
return isReadOnly ? null : (
|
||||
<AccountsActionsDropdown
|
||||
assetId={assetId}
|
||||
assetContractAddress={
|
||||
@@ -294,13 +296,11 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
onClickBreakdown,
|
||||
onClickDeposit,
|
||||
onClickWithdraw,
|
||||
props.isReadOnly,
|
||||
isReadOnly,
|
||||
showDepositButton,
|
||||
]);
|
||||
|
||||
const data = rowData?.filter(
|
||||
(data) => data.asset.id !== props.pinnedAsset?.id
|
||||
);
|
||||
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
@@ -318,7 +318,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
}}
|
||||
columnDefs={colDefs}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
|
||||
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,12 @@ describe('Announcements', () => {
|
||||
const { container } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
await act(
|
||||
async () =>
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not display the banner when there are no announcements', async () => {
|
||||
@@ -42,9 +45,12 @@ describe('Announcements', () => {
|
||||
const { container } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
await act(
|
||||
async () =>
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the correct announcement', async () => {
|
||||
@@ -200,8 +206,11 @@ describe('Announcements', () => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Live text')).not.toBeInTheDocument();
|
||||
});
|
||||
await act(
|
||||
async () =>
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Live text')).not.toBeInTheDocument();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,4 +80,14 @@ describe('AssetDetailsTable', () => {
|
||||
}
|
||||
}
|
||||
);
|
||||
it('omits specified rows when omitRows prop is provided', async () => {
|
||||
const asset = generateERC20Asset(1, Schema.AssetStatus.STATUS_ENABLED);
|
||||
const omittedKeys = [AssetDetail.TYPE, AssetDetail.DECIMALS];
|
||||
render(<AssetDetailsTable asset={asset} omitRows={omittedKeys} />);
|
||||
|
||||
for (const key of omittedKeys) {
|
||||
expect(screen.queryByTestId(testId(key, 'label'))).toBeNull();
|
||||
expect(screen.queryByTestId(testId(key, 'value'))).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,9 +224,11 @@ export const testId = (detail: AssetDetail, field: 'label' | 'value') =>
|
||||
|
||||
export type AssetDetailsTableProps = {
|
||||
asset: Asset;
|
||||
omitRows?: AssetDetail[];
|
||||
} & Omit<KeyValueTableRowProps, 'children'>;
|
||||
export const AssetDetailsTable = ({
|
||||
asset,
|
||||
omitRows = [],
|
||||
...props
|
||||
}: AssetDetailsTableProps) => {
|
||||
const longStringModifiers = (key: AssetDetail, value: string) =>
|
||||
@@ -243,7 +245,7 @@ export const AssetDetailsTable = ({
|
||||
return (
|
||||
<KeyValueTable>
|
||||
{details
|
||||
.filter(({ value }) => Boolean(value))
|
||||
.filter(({ key, value }) => Boolean(value) && !omitRows.includes(key))
|
||||
.map(({ key, label, value, tooltip, valueTooltip }) => (
|
||||
<KeyValueTableRow key={key} {...props}>
|
||||
<div
|
||||
|
||||
@@ -78,4 +78,244 @@ const candles: CandleFieldsFragment[] = [
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:00:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:01:00Z',
|
||||
high: '17481092',
|
||||
low: '17403651',
|
||||
open: '17458833',
|
||||
close: '17446470',
|
||||
volume: '82721',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:10:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:11:00Z',
|
||||
high: '17491202',
|
||||
low: '17361138',
|
||||
open: '17446470',
|
||||
close: '17367174',
|
||||
volume: '62637',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:20:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:21:00Z',
|
||||
high: '17424522',
|
||||
low: '17337719',
|
||||
open: '17367174',
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:30:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:31:00Z',
|
||||
high: '17500000',
|
||||
low: '17300000',
|
||||
open: '17380000',
|
||||
close: '17450000',
|
||||
volume: '70000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:40:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:41:00Z',
|
||||
high: '17400000',
|
||||
low: '17350000',
|
||||
open: '17360000',
|
||||
close: '17390000',
|
||||
volume: '55000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:50:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:51:00Z',
|
||||
high: '17481092',
|
||||
low: '17403651',
|
||||
open: '17458833',
|
||||
close: '17446470',
|
||||
volume: '82721',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T10:00:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T10:01:00Z',
|
||||
high: '17491202',
|
||||
low: '17361138',
|
||||
open: '17446470',
|
||||
close: '17367174',
|
||||
volume: '62637',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T10:10:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T10:11:00Z',
|
||||
high: '17424522',
|
||||
low: '17337719',
|
||||
open: '17367174',
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T10:20:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T10:21:00Z',
|
||||
high: '17500000',
|
||||
low: '17300000',
|
||||
open: '17380000',
|
||||
close: '17450000',
|
||||
volume: '70000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T10:30:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T10:31:00Z',
|
||||
high: '17400000',
|
||||
low: '17350000',
|
||||
open: '17360000',
|
||||
close: '17390000',
|
||||
volume: '55000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T01:00:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:01:00Z',
|
||||
high: '17481092',
|
||||
low: '17403651',
|
||||
open: '17458833',
|
||||
close: '17446470',
|
||||
volume: '82721',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:10:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:11:00Z',
|
||||
high: '17491202',
|
||||
low: '17361138',
|
||||
open: '17446470',
|
||||
close: '17367174',
|
||||
volume: '62637',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:20:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:21:00Z',
|
||||
high: '17424522',
|
||||
low: '17337719',
|
||||
open: '17367174',
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:30:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:31:00Z',
|
||||
high: '17500000',
|
||||
low: '17300000',
|
||||
open: '17380000',
|
||||
close: '17450000',
|
||||
volume: '70000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:40:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:41:00Z',
|
||||
high: '17400000',
|
||||
low: '17350000',
|
||||
open: '17360000',
|
||||
close: '17390000',
|
||||
volume: '55000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T09:50:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T09:51:00Z',
|
||||
high: '17481092',
|
||||
low: '17403651',
|
||||
open: '17458833',
|
||||
close: '17446470',
|
||||
volume: '82721',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-05T20:00:00Z',
|
||||
lastUpdateInPeriod: '2022-04-05T20:01:00Z',
|
||||
high: '17491202',
|
||||
low: '17361138',
|
||||
open: '17446470',
|
||||
close: '17367174',
|
||||
volume: '62637',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-05T22:10:00Z',
|
||||
lastUpdateInPeriod: '2022-04-05T24:11:00Z',
|
||||
high: '17424522',
|
||||
low: '17337719',
|
||||
open: '17367174',
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-05T21:20:00Z',
|
||||
lastUpdateInPeriod: '2022-04-05T21:21:00Z',
|
||||
high: '17500000',
|
||||
low: '17300000',
|
||||
open: '17380000',
|
||||
close: '17450000',
|
||||
volume: '70000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-05T21:30:00Z',
|
||||
lastUpdateInPeriod: '2022-04-05T21:31:00Z',
|
||||
high: '17400000',
|
||||
low: '17350000',
|
||||
open: '17360000',
|
||||
close: '17390000',
|
||||
volume: '55000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T02:00:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T02:01:00Z',
|
||||
high: '17491202',
|
||||
low: '17361138',
|
||||
open: '17446470',
|
||||
close: '17367174',
|
||||
volume: '62637',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T03:03:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T03:11:00Z',
|
||||
high: '17424522',
|
||||
low: '17337719',
|
||||
open: '17367174',
|
||||
close: '17376455',
|
||||
volume: '60259',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T00:20:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T00:21:00Z',
|
||||
high: '17500000',
|
||||
low: '17300000',
|
||||
open: '17380000',
|
||||
close: '17450000',
|
||||
volume: '70000',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
periodStart: '2022-04-06T01:30:00Z',
|
||||
lastUpdateInPeriod: '2022-04-06T01:31:00Z',
|
||||
high: '17400000',
|
||||
low: '17350000',
|
||||
open: '17360000',
|
||||
close: '17390000',
|
||||
volume: '55000',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,7 +8,6 @@ export * from './lib/cells/numeric-cell';
|
||||
export * from './lib/cells/price-cell';
|
||||
export * from './lib/cells/price-change-cell';
|
||||
export * from './lib/cells/price-flash-cell';
|
||||
export * from './lib/cells/vol-cell';
|
||||
export * from './lib/cells/centered-grid-cell';
|
||||
export * from './lib/cells/market-name-cell';
|
||||
export * from './lib/cells/order-type-cell';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { memo } from 'react';
|
||||
import { BID_COLOR, ASK_COLOR } from './vol-cell';
|
||||
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
|
||||
import { NumericCell } from './numeric-cell';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
|
||||
const BID_COLOR = theme.colors.vega.green.DEFAULT;
|
||||
const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
|
||||
export interface CumulativeVolProps {
|
||||
ask?: number;
|
||||
bid?: number;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VolCell } from './vol-cell';
|
||||
import * as tailwind from '@vegaprotocol/tailwindcss-config';
|
||||
|
||||
describe('VolCell', () => {
|
||||
const significantPart = '12,345';
|
||||
const decimalPart = '67';
|
||||
const props = {
|
||||
value: 1234567,
|
||||
valueFormatted: `${significantPart}.${decimalPart}`,
|
||||
type: 'ask' as const,
|
||||
testId: 'cell',
|
||||
};
|
||||
|
||||
it('Displays formatted value', () => {
|
||||
render(<VolCell {...props} />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent(
|
||||
props.valueFormatted
|
||||
);
|
||||
expect(screen.getByText(decimalPart)).toBeInTheDocument();
|
||||
expect(screen.getByText(decimalPart)).toHaveClass('opacity-60');
|
||||
});
|
||||
|
||||
it('Displays 0', () => {
|
||||
render(<VolCell {...props} value={0} valueFormatted="0.00" />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent('0.00');
|
||||
});
|
||||
|
||||
it('Displays - if value is not a number', () => {
|
||||
render(<VolCell {...props} value={null} valueFormatted="" />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent('-');
|
||||
});
|
||||
|
||||
it('renders bid volume bar', () => {
|
||||
render(<VolCell {...props} type="bid" />);
|
||||
expect(screen.getByTestId('vol-bar')).toHaveClass('left-0'); // renders bid bars from the left
|
||||
expect(screen.getByTestId('vol-bar')).toHaveStyle({
|
||||
backgroundColor: tailwind.theme.colors.vega.green.DEFAULT,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders ask volume bar', () => {
|
||||
render(<VolCell {...props} type="ask" />);
|
||||
expect(screen.getByTestId('vol-bar')).toHaveClass('right-0'); // renders ask bars from the right
|
||||
expect(screen.getByTestId('vol-bar')).toHaveStyle({
|
||||
backgroundColor: tailwind.theme.colors.vega.pink.DEFAULT,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import { memo } from 'react';
|
||||
import type { ICellRendererParams } from 'ag-grid-community';
|
||||
import classNames from 'classnames';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
import { NumericCell } from './numeric-cell';
|
||||
|
||||
export interface VolCellProps {
|
||||
value: number | bigint | null | undefined;
|
||||
valueFormatted: string;
|
||||
relativeValue?: number;
|
||||
type: 'ask' | 'bid';
|
||||
testId?: string;
|
||||
}
|
||||
export interface IVolCellProps extends ICellRendererParams {
|
||||
value: number | bigint | null | undefined;
|
||||
valueFormatted: Omit<VolCellProps, 'value'>;
|
||||
}
|
||||
|
||||
export const BID_COLOR = theme.colors.vega.green.DEFAULT;
|
||||
export const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
|
||||
|
||||
export const VolCell = memo(
|
||||
({ value, valueFormatted, relativeValue, type, testId }: VolCellProps) => {
|
||||
if ((!value && value !== 0) || isNaN(Number(value))) {
|
||||
return <div data-testid={testId || 'vol'}>-</div>;
|
||||
}
|
||||
return (
|
||||
<div className="relative" data-testid={testId || 'vol'}>
|
||||
<div
|
||||
data-testid="vol-bar"
|
||||
className={classNames(
|
||||
'h-full absolute top-0 opacity-40 dark:opacity-100',
|
||||
{
|
||||
'left-0': type === 'bid',
|
||||
'right-0': type === 'ask',
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
width: relativeValue ? `${relativeValue}%` : '0%',
|
||||
backgroundColor: type === 'bid' ? BID_COLOR : ASK_COLOR,
|
||||
opacity: 0.6,
|
||||
}}
|
||||
/>
|
||||
<NumericCell value={value} valueFormatted={valueFormatted} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
VolCell.displayName = 'VolCell';
|
||||
@@ -7,6 +7,6 @@ export const COL_DEFS = {
|
||||
minWidth: 45,
|
||||
maxWidth: 45,
|
||||
type: 'rightAligned',
|
||||
pinned: 'right',
|
||||
pinned: 'right' as const,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,18 +4,17 @@ import type {
|
||||
ValueFormatterParams,
|
||||
ValueGetterParams,
|
||||
} from 'ag-grid-community';
|
||||
import type { IDatasource, IGetRowsParams, RowNode } from 'ag-grid-community';
|
||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
||||
import type { AgGridReactProps } from 'ag-grid-react';
|
||||
|
||||
type Field = string | readonly string[];
|
||||
|
||||
type RowHelper<TObj, TRow, TField extends Field> = Omit<
|
||||
TObj,
|
||||
'data' | 'value' | 'node'
|
||||
'data' | 'value'
|
||||
> & {
|
||||
data?: TRow;
|
||||
value?: Get<TRow, TField>;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
@@ -24,12 +23,8 @@ export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
TField
|
||||
>;
|
||||
|
||||
export type VegaValueGetterParams<TRow> = Omit<
|
||||
ValueGetterParams,
|
||||
'data' | 'node'
|
||||
> & {
|
||||
export type VegaValueGetterParams<TRow> = Omit<ValueGetterParams, 'data'> & {
|
||||
data?: TRow;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
truncateByChars,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
@@ -21,51 +21,46 @@ export const DepositsTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<DepositFieldsFragment>
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||
<AgGridColumn
|
||||
headerName="Amount"
|
||||
field="amount"
|
||||
valueFormatter={({
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: 'Asset', field: 'asset.symbol' },
|
||||
{
|
||||
headerName: 'Amount',
|
||||
field: 'amount',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => {
|
||||
return isNumeric(value) && data
|
||||
? addDecimalsFormatNumber(value, data.asset.decimals)
|
||||
: null;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Created at"
|
||||
field="createdTimestamp"
|
||||
valueFormatter={({
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Created at',
|
||||
field: 'createdTimestamp',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
DepositFieldsFragment,
|
||||
'createdTimestamp'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Status"
|
||||
field="status"
|
||||
valueFormatter={({
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Status',
|
||||
field: 'status',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => {
|
||||
return value ? DepositStatusMapping[value] : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Tx hash"
|
||||
field="txHash"
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Tx hash',
|
||||
field: 'txHash',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
|
||||
@@ -76,9 +71,19 @@ export const DepositsTable = forwardRef<
|
||||
{truncateByChars(value)}
|
||||
</EtherscanLink>
|
||||
);
|
||||
}}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
columnDefs={columnDefs}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Networks } from '../types';
|
||||
import { useEnvironment } from './use-environment';
|
||||
import { stripFullStops } from '@vegaprotocol/utils';
|
||||
|
||||
const VEGA_DOCS_URL = process.env['NX_VEGA_DOCS_URL'] || '';
|
||||
const VEGA_DOCS_URL =
|
||||
process.env['NX_VEGA_DOCS_URL'] || 'https://docs.vega.xyz/mainnet';
|
||||
|
||||
type Net = Exclude<Networks, 'CUSTOM'>;
|
||||
export enum DApp {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
GITHUB_VEGA_DEV_RELEASES_DATA,
|
||||
} from './mocks/github-releases';
|
||||
import { useVegaRelease } from './use-vega-release';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
describe('useVegaRelease', () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,8 +32,11 @@ describe('useVegaRelease', () => {
|
||||
|
||||
it('should return undefined when a release cannot be found', async () => {
|
||||
const { result } = renderHook(() => useVegaRelease('v0.70.1'));
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual(undefined);
|
||||
});
|
||||
await act(
|
||||
async () =>
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual(undefined);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import type {
|
||||
AgGridReact,
|
||||
AgGridReactProps,
|
||||
AgReactUiProps,
|
||||
} from 'ag-grid-react';
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
import type { ITooltipParams, ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
positiveClassNames,
|
||||
@@ -43,29 +43,19 @@ export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
|
||||
export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
({ partyId, onMarketClick, ...props }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
tooltipHideDelay={2000}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.name"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
type="rightAligned"
|
||||
field="size"
|
||||
cellClassRules={{
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.name',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
type: 'rightAligned',
|
||||
field: 'size',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: Trade }) => {
|
||||
const partySide = getPartySide(data, partyId);
|
||||
return partySide === 'buyer';
|
||||
@@ -74,48 +64,47 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
const partySide = getPartySide(data, partyId);
|
||||
return partySide === 'seller';
|
||||
},
|
||||
}}
|
||||
valueFormatter={formatSize(partyId)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Price')}
|
||||
field="price"
|
||||
valueFormatter={formatPrice}
|
||||
type="rightAligned"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Notional')}
|
||||
field="price"
|
||||
valueFormatter={formatTotal}
|
||||
type="rightAligned"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Role')}
|
||||
field="aggressor"
|
||||
valueFormatter={formatRole(partyId)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Fee')}
|
||||
field="market.tradableInstrument.instrument.product"
|
||||
valueFormatter={formatFee(partyId)}
|
||||
type="rightAligned"
|
||||
tooltipField="market.tradableInstrument.instrument.product"
|
||||
tooltipComponent={FeesBreakdownTooltip}
|
||||
tooltipComponentParams={{ partyId }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Date')}
|
||||
field="createdAt"
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: formatSize(partyId),
|
||||
},
|
||||
{
|
||||
headerName: t('Price'),
|
||||
field: 'price',
|
||||
valueFormatter: formatPrice,
|
||||
type: 'rightAligned',
|
||||
},
|
||||
{
|
||||
headerName: t('Notional'),
|
||||
field: 'price',
|
||||
valueFormatter: formatTotal,
|
||||
type: 'rightAligned',
|
||||
},
|
||||
{
|
||||
headerName: t('Role'),
|
||||
field: 'aggressor',
|
||||
valueFormatter: formatRole(partyId),
|
||||
},
|
||||
{
|
||||
headerName: t('Fee'),
|
||||
field: 'market.tradableInstrument.instrument.product',
|
||||
valueFormatter: formatFee(partyId),
|
||||
type: 'rightAligned',
|
||||
tooltipField: 'market.tradableInstrument.instrument.product',
|
||||
tooltipComponent: FeesBreakdownTooltip,
|
||||
tooltipComponentParams: { partyId },
|
||||
},
|
||||
{
|
||||
headerName: t('Date'),
|
||||
field: 'createdAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="fill-actions"
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Trade, 'id'>) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'fill-actions',
|
||||
cellRenderer: ({ data }: VegaICellRendererParams<Trade, 'id'>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<FillActionsDropdown
|
||||
@@ -124,9 +113,25 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
tradeId={data.id}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
...COL_DEFS.actions,
|
||||
},
|
||||
],
|
||||
[onMarketClick, partyId]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
columnDefs={columnDefs}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
tooltipHideDelay={2000}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
AccountTypeMapping,
|
||||
DescriptionTransferTypeMapping,
|
||||
TransferTypeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { formatRFC3339, subDays } from 'date-fns';
|
||||
|
||||
export const TransferTooltipCellComponent = ({
|
||||
@@ -47,6 +47,143 @@ type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||
|
||||
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
(props, ref) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Sender'),
|
||||
field: 'fromAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'fromAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketSender.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver'),
|
||||
field: 'toAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'toAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Transfer type'),
|
||||
field: 'transferType',
|
||||
tooltipField: 'transferType',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: TransferTypeMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Quantity'),
|
||||
field: 'quantity',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'assetId',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || '',
|
||||
},
|
||||
{
|
||||
headerName: t('Sender account balance'),
|
||||
field: 'fromAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver account balance'),
|
||||
field: 'toAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Vega time'),
|
||||
field: 'vegaTime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
|
||||
filterParams: dateRangeFilterParams,
|
||||
filter: DateRangeFilter,
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@@ -61,148 +198,9 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
buttons: ['reset'],
|
||||
},
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Sender')}
|
||||
field="fromAccountPartyId"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || '')
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Account type')}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: AccountTypeMapping,
|
||||
}}
|
||||
field="fromAccountType"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-'
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketSender.tradableInstrument.instrument.code"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-'}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Receiver')}
|
||||
field="toAccountPartyId"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || '')
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Account type')}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: AccountTypeMapping,
|
||||
}}
|
||||
field="toAccountType"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-'
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketReceiver.tradableInstrument.instrument.code"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-'}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Transfer type')}
|
||||
field="transferType"
|
||||
tooltipField="transferType"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: TransferTypeMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : ''
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Quantity')}
|
||||
field="quantity"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Asset')}
|
||||
field="assetId"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Sender account balance')}
|
||||
field="fromAccountBalance"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Receiver account balance')}
|
||||
field="toAccountBalance"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Vega time')}
|
||||
field="vegaTime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
|
||||
}
|
||||
filterParams={dateRangeFilterParams}
|
||||
filter={DateRangeFilter}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => data.id}
|
||||
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
|
||||
@@ -67,6 +67,7 @@ export const liquidityProviderFeeShareQuery = (
|
||||
|
||||
export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
|
||||
{
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
accountsConnection: {
|
||||
@@ -92,6 +93,7 @@ export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
|
||||
__typename: 'LiquidityProvision',
|
||||
},
|
||||
{
|
||||
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
party: {
|
||||
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
accountsConnection: {
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('useOracleMarkets', () => {
|
||||
it('returns correct market list for the given provider', () => {
|
||||
mockMarkets.mockReturnValueOnce({ data: marketsData });
|
||||
const { result } = renderHook(() => useOracleMarkets(mockProvider));
|
||||
console.log(JSON.stringify(result.current));
|
||||
expect(result.current).toStrictEqual(oracleMarkets);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,7 +181,8 @@ it('displays realised and unrealised PNL', async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[10].textContent).toEqual('4.56');
|
||||
expect(cells[9].textContent).toEqual('12.3');
|
||||
expect(cells[10].textContent).toEqual('45.6');
|
||||
});
|
||||
|
||||
it('displays close button', async () => {
|
||||
|
||||
@@ -364,14 +364,20 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.realisedPNL, data.decimals).toNumber();
|
||||
: toBigNum(
|
||||
data.realisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'realisedPNL'>) => {
|
||||
return !data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
|
||||
: addDecimalsFormatNumber(
|
||||
data.realisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
},
|
||||
headerTooltip: t(
|
||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||
@@ -389,14 +395,20 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.unrealisedPNL, data.decimals).toNumber();
|
||||
: toBigNum(
|
||||
data.unrealisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'unrealisedPNL'>) =>
|
||||
!data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals),
|
||||
: addDecimalsFormatNumber(
|
||||
data.unrealisedPNL,
|
||||
data.marketDecimalPlaces
|
||||
),
|
||||
headerTooltip: t(
|
||||
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { forwardRef } from 'react';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
@@ -47,85 +48,90 @@ interface Props extends AgGridReactProps {
|
||||
onClick?: (price?: string) => void;
|
||||
}
|
||||
|
||||
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data.id}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Price')}
|
||||
field="price"
|
||||
type="rightAligned"
|
||||
width={130}
|
||||
cellClass={changeCellClass}
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Trade, 'price'>) => {
|
||||
if (!value || !data?.market) {
|
||||
return null;
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
}}
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<Trade, 'price'>) => {
|
||||
if (!data?.market || !value) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={() =>
|
||||
props.onClick &&
|
||||
props.onClick(
|
||||
addDecimal(value, data.market?.decimalPlaces || 0)
|
||||
)
|
||||
}
|
||||
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
|
||||
>
|
||||
{addDecimalsFormatNumber(value, data.market.decimalPlaces)}
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
field="size"
|
||||
width={125}
|
||||
type="rightAligned"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Trade, 'size'>) => {
|
||||
if (!value || !data?.market) {
|
||||
return null;
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
export const TradesTable = forwardRef<AgGridReact, Props>(
|
||||
({ onClick, ...props }, ref) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Price'),
|
||||
field: 'price',
|
||||
type: 'rightAligned',
|
||||
width: 130,
|
||||
cellClass: changeCellClass,
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data.market.positionDecimalPlaces
|
||||
);
|
||||
data,
|
||||
}: VegaValueFormatterParams<Trade, 'price'>) => {
|
||||
if (!value || !data?.market) {
|
||||
return '';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<Trade, 'price'>) => {
|
||||
if (!data?.market || !value) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={() =>
|
||||
onClick &&
|
||||
onClick(addDecimal(value, data.market?.decimalPlaces || 0))
|
||||
}
|
||||
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
|
||||
>
|
||||
{addDecimalsFormatNumber(value, data.market.decimalPlaces)}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'size',
|
||||
width: 125,
|
||||
type: 'rightAligned',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Trade, 'size'>) => {
|
||||
if (!value || !data?.market) {
|
||||
return '';
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
value,
|
||||
data.market.positionDecimalPlaces
|
||||
);
|
||||
},
|
||||
cellRenderer: NumericCell,
|
||||
},
|
||||
{
|
||||
headerName: t('Created at'),
|
||||
field: 'createdAt',
|
||||
type: 'rightAligned',
|
||||
width: 170,
|
||||
cellClass: 'text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
|
||||
return value && getDateTimeFormat().format(new Date(value));
|
||||
},
|
||||
},
|
||||
],
|
||||
[onClick]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data.id}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
cellRenderer={NumericCell}
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created at')}
|
||||
field="createdAt"
|
||||
type="rightAligned"
|
||||
width={170}
|
||||
cellClass="text-right"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
|
||||
return value && getDateTimeFormat().format(new Date(value));
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
});
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/ui-toolkit",
|
||||
"version": "0.12.5"
|
||||
"version": "0.12.6"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Tooltip } from '../tooltip';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const TOOLTIP_TIMEOUT = 800;
|
||||
|
||||
@@ -11,22 +11,7 @@ export interface CopyWithTooltipProps {
|
||||
}
|
||||
|
||||
export function CopyWithTooltip({ children, text }: CopyWithTooltipProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line
|
||||
let timeout: any;
|
||||
|
||||
if (copied) {
|
||||
timeout = setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, TOOLTIP_TIMEOUT);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [copied]);
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
|
||||
return (
|
||||
<CopyToClipboard text={text} onCopy={() => setCopied(true)}>
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { getIntentBorder } from '../../utils/intent';
|
||||
import { Icon } from '../icon';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Intent } from '../../utils/intent';
|
||||
@@ -77,7 +77,7 @@ export function Dialog({
|
||||
className="absolute p-2 top-0 right-0 md:top-2 md:right-2"
|
||||
data-testid="dialog-close"
|
||||
>
|
||||
<Icon name="cross" />
|
||||
<VegaIcon name={VegaIconNames.CROSS} />
|
||||
</DialogPrimitives.Close>
|
||||
)}
|
||||
<div className="flex gap-4 max-w-full">
|
||||
|
||||
@@ -3,7 +3,6 @@ import classNames from 'classnames';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
import { Icon } from '../icon';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -140,7 +139,7 @@ export const DropdownMenuItemIndicator = forwardRef<
|
||||
ref={forwardedRef}
|
||||
className="flex-end"
|
||||
>
|
||||
<Icon name="tick" />
|
||||
<VegaIcon name={VegaIconNames.TICK} />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
));
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ const Target = ({
|
||||
return (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<div className="text-vega-dark-100 dark:text-vega-light-200">
|
||||
<div className="mt-1.5 inline-flex">
|
||||
<Indicator variant={Intent.None} />
|
||||
</div>
|
||||
<span>
|
||||
{t('Target stake')} {addDecimalsFormatNumber(target, decimals)}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
@@ -195,9 +195,11 @@ export const HealthBar = ({
|
||||
{showRemainder && <Remainder />}
|
||||
{showOverflow && (
|
||||
<Tooltip
|
||||
description={t(
|
||||
'Providers greater than 2x target stake not shown'
|
||||
)}
|
||||
description={
|
||||
<div className="text-vega-dark-100 dark:text-vega-light-200">
|
||||
t( 'Providers greater than 2x target stake not shown' )
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="h-[inherit] relative flex-1 leading-4">...</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const IconTick = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M6 11.2505L13.6252 3.62523L14.3748 4.37477L6 12.7495L1.62523 8.37477L2.37476 7.62523L6 11.2505Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import { IconCross } from './svg-icons/icon-cross';
|
||||
import { IconKebab } from './svg-icons/icon-kebab';
|
||||
import { IconArrowDown } from './svg-icons/icon-arrow-down';
|
||||
import { IconChevronDown } from './svg-icons/icon-chevron-down';
|
||||
import { IconTick } from './svg-icons/icon-tick';
|
||||
|
||||
export enum VegaIconNames {
|
||||
BREAKDOWN = 'breakdown',
|
||||
@@ -40,6 +41,7 @@ export enum VegaIconNames {
|
||||
TREND_UP = 'trend-up',
|
||||
CROSS = 'cross',
|
||||
KEBAB = 'kebab',
|
||||
TICK = 'tick',
|
||||
}
|
||||
|
||||
export const VegaIconNameMap: Record<
|
||||
@@ -66,4 +68,5 @@ export const VegaIconNameMap: Record<
|
||||
'trend-up': IconTrendUp,
|
||||
cross: IconCross,
|
||||
kebab: IconKebab,
|
||||
tick: IconTick,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Button } from '../button';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export function truncateMiddle(address: string) {
|
||||
export function truncateMiddle(address: string, start = 6, end = 4) {
|
||||
if (address.length < 11) return address;
|
||||
return (
|
||||
address.slice(0, 6) +
|
||||
address.slice(0, start) +
|
||||
'\u2026' +
|
||||
address.slice(address.length - 4, address.length)
|
||||
address.slice(address.length - end, address.length)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@vegaprotocol/utils",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ describe('truncateByChars', () => {
|
||||
},
|
||||
{ i: '12345678901234567890', s: 0, e: 10, o: `${ELLIPSIS}1234567890` },
|
||||
{ i: '123', s: 0, e: 4, o: '123' },
|
||||
])('should truncate given string by specific chars', ({ i, s, e, o }) => {
|
||||
{ i: '12345678901234567890', s: 3, e: 0, o: `123${ELLIPSIS}` },
|
||||
])('should truncate given string by specific chars: %s', ({ i, s, e, o }) => {
|
||||
expect(truncateByChars(i, s, e)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
@@ -28,7 +29,7 @@ describe('shorten', () => {
|
||||
{ i: '12345678901234567890', l: 10, o: `123456789${ELLIPSIS}` },
|
||||
{ i: '12345678901234567890', l: 20, o: `1234567890123456789${ELLIPSIS}` },
|
||||
{ i: '12345678901234567890', l: 30, o: `12345678901234567890` },
|
||||
])('should shorten given string by specific limit', ({ i, l, o }) => {
|
||||
])('should shorten given string by specific limit: %s', ({ i, l, o }) => {
|
||||
const output = shorten(i, l);
|
||||
expect(output).toStrictEqual(o);
|
||||
});
|
||||
@@ -51,7 +52,7 @@ describe('titlefy', () => {
|
||||
words: ['VEGAUSD', '123.22'],
|
||||
o: 'VEGAUSD - 123.22 - Vega',
|
||||
},
|
||||
])('should convert to title-like string', ({ words, o }) => {
|
||||
])('should convert to title-like string: %s', ({ words, o }) => {
|
||||
expect(titlefy(words)).toEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,10 @@ export function truncateByChars(input: string, start = 6, end = 6) {
|
||||
if (input.length <= start + end + 1) {
|
||||
return input;
|
||||
}
|
||||
return input.slice(0, start) + ELLIPSIS + input.slice(-end);
|
||||
|
||||
const s = input.slice(0, start);
|
||||
const e = end !== 0 ? input.slice(-end) : '';
|
||||
return `${s}${ELLIPSIS}${e}`;
|
||||
}
|
||||
|
||||
export function shorten(input: string, limit?: number) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
Dialog,
|
||||
Icon,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Splash,
|
||||
@@ -32,7 +31,7 @@ export const WithdrawalApprovalDialog = ({
|
||||
return (
|
||||
<Dialog
|
||||
title={t('Save withdrawal details')}
|
||||
icon={<Icon name="info-sign"></Icon>}
|
||||
icon={<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />}
|
||||
open={open}
|
||||
onChange={(isOpen) => onChange(isOpen)}
|
||||
onCloseAutoFocus={(e) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, useMemo } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
convertToCountdownString,
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -35,20 +34,111 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { TimestampedWithdrawals } from './use-ready-to-complete-withdrawals-toast';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const WithdrawalsTable = (
|
||||
props: TypedDataAgGrid<WithdrawalFieldsFragment> & {
|
||||
ready?: TimestampedWithdrawals;
|
||||
delayed?: TimestampedWithdrawals;
|
||||
}
|
||||
) => {
|
||||
export const WithdrawalsTable = ({
|
||||
delayed,
|
||||
ready,
|
||||
...props
|
||||
}: TypedDataAgGrid<WithdrawalFieldsFragment> & {
|
||||
ready?: TimestampedWithdrawals;
|
||||
delayed?: TimestampedWithdrawals;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
||||
(store) => store.create
|
||||
);
|
||||
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: 'Asset', field: 'asset.symbol' },
|
||||
{
|
||||
headerName: t('Amount'),
|
||||
field: 'amount',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'amount'>) => {
|
||||
return isNumeric(value) && data?.asset
|
||||
? addDecimalsFormatNumber(value, data.asset.decimals)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Recipient'),
|
||||
field: 'details.receiverAddress',
|
||||
cellRenderer: 'RecipientCell',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'details.receiverAddress'
|
||||
>) => {
|
||||
if (!data) return '';
|
||||
if (!value) return '-';
|
||||
return truncateByChars(value);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Created'),
|
||||
field: 'createdTimestamp',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'createdTimestamp'
|
||||
>) =>
|
||||
data
|
||||
? value
|
||||
? getDateTimeFormat().format(new Date(value))
|
||||
: '-'
|
||||
: '',
|
||||
},
|
||||
{
|
||||
headerName: t('Completed'),
|
||||
field: 'withdrawnTimestamp',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'withdrawnTimestamp'
|
||||
>) =>
|
||||
data
|
||||
? value
|
||||
? getDateTimeFormat().format(new Date(value))
|
||||
: '-'
|
||||
: '',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'status',
|
||||
cellRenderer: 'StatusCell',
|
||||
cellRendererParams: { ready, delayed },
|
||||
},
|
||||
{
|
||||
headerName: t('Transaction'),
|
||||
field: 'txHash',
|
||||
flex: 2,
|
||||
type: 'rightAligned',
|
||||
cellRendererParams: {
|
||||
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
||||
createWithdrawApproval(withdrawal);
|
||||
},
|
||||
},
|
||||
cellRendererSelector: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<WithdrawalFieldsFragment>) => ({
|
||||
component: data?.txHash ? 'EtherscanLinkCell' : 'CompleteCell',
|
||||
}),
|
||||
},
|
||||
],
|
||||
[createWithdrawApproval, delayed, ready]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
overlayNoRowsTemplate={t('No withdrawals')}
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
components={{
|
||||
@@ -60,93 +150,7 @@ export const WithdrawalsTable = (
|
||||
suppressCellFocus
|
||||
ref={gridRef}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||
<AgGridColumn
|
||||
headerName={t('Amount')}
|
||||
field="amount"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'amount'>) => {
|
||||
return isNumeric(value) && data?.asset
|
||||
? addDecimalsFormatNumber(value, data.asset.decimals)
|
||||
: '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Recipient')}
|
||||
field="details.receiverAddress"
|
||||
cellRenderer="RecipientCell"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'details.receiverAddress'
|
||||
>) => {
|
||||
if (!data) return null;
|
||||
if (!value) return '-';
|
||||
return truncateByChars(value);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created')}
|
||||
field="createdTimestamp"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'createdTimestamp'
|
||||
>) =>
|
||||
data
|
||||
? value
|
||||
? getDateTimeFormat().format(new Date(value))
|
||||
: '-'
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Completed')}
|
||||
field="withdrawnTimestamp"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFieldsFragment,
|
||||
'withdrawnTimestamp'
|
||||
>) =>
|
||||
data
|
||||
? value
|
||||
? getDateTimeFormat().format(new Date(value))
|
||||
: '-'
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="status"
|
||||
cellRendererParams={{ ready: props.ready, delayed: props.delayed }}
|
||||
cellRenderer="StatusCell"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Transaction')}
|
||||
field="txHash"
|
||||
flex={2}
|
||||
type="rightAligned"
|
||||
cellRendererParams={{
|
||||
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
||||
createWithdrawApproval(withdrawal);
|
||||
},
|
||||
}}
|
||||
cellRendererSelector={({
|
||||
data,
|
||||
}: VegaICellRendererParams<WithdrawalFieldsFragment>) => ({
|
||||
component: data?.txHash ? 'EtherscanLinkCell' : 'CompleteCell',
|
||||
})}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -193,9 +197,8 @@ export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<Icon name="info-sign" size={4} /> {t('View withdrawal details')}
|
||||
</span>
|
||||
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
|
||||
{t('View withdrawal details')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
+2
-2
@@ -46,8 +46,8 @@
|
||||
"@web3-react/metamask": "^8.1.2-beta.0",
|
||||
"@web3-react/walletconnect": "8.1.3-beta.0",
|
||||
"@web3-react/walletconnect-v2": "^8.1.3-beta.0",
|
||||
"ag-grid-community": "^27.0.1",
|
||||
"ag-grid-react": "^27.0.1",
|
||||
"ag-grid-community": "^29.3.5",
|
||||
"ag-grid-react": "^29.3.5",
|
||||
"allotment": "1.18.1",
|
||||
"alpha-lyrae": "vegaprotocol/alpha-lyrae",
|
||||
"apollo": "^2.33.9",
|
||||
|
||||
@@ -8809,15 +8809,15 @@ aes-js@^3.1.2:
|
||||
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
|
||||
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
|
||||
|
||||
ag-grid-community@^27.0.1:
|
||||
version "27.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.3.0.tgz#b1e94a58026aaf2f0cd7920e35833325b5e762c7"
|
||||
integrity sha512-R5oZMXEHXnOLrmhn91J8lR0bv6IAnRcU6maO+wKLMJxffRWaAYFAuw1jt7bdmcKCv8c65F6LEBx4ykSOALa9vA==
|
||||
ag-grid-community@^29.3.5:
|
||||
version "29.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-29.3.5.tgz#16897896d10fa3ecac79279aad50d3aaa17c5f33"
|
||||
integrity sha512-LxUo21f2/CH31ACEs1C7Q/ggGGI1fQPSTB4aY5OThmM+lBkygZ7QszBE8jpfgWOIjvjdtcdIeQbmbjkHeMsA7A==
|
||||
|
||||
ag-grid-react@^27.0.1:
|
||||
version "27.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-27.3.0.tgz#fe06647653f8b0b349b8e613aab8ea2e07915562"
|
||||
integrity sha512-2bs9YfJ/shvBZQLLjny4NFvht+ic6VtpTPO0r3bHHOhlL3Fjx2rGvS6AHSwfvu+kJacHCta30PjaEbX8T3UDyw==
|
||||
ag-grid-react@^29.3.5:
|
||||
version "29.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-29.3.5.tgz#0eae8934d372c7751e98789542fc663aee0ad6ad"
|
||||
integrity sha512-Eg0GJ8hEBuxdVaN5g+qITOzhw0MGL9avL0Oaajr+p7QRtq2pIFHLZSknWsCBzUTjidiu75WZMKwlZjtGEuafdQ==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user