Compare commits

..
Author SHA1 Message Date
Matthew Russell edcba24399 fix: use lp id for row id and update func 2023-06-26 12:22:33 -07:00
106 changed files with 2066 additions and 2252 deletions
+23 -45
View File
@@ -6,9 +6,7 @@ on:
- release/*
- develop
- main
# uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
# pull_request:
pull_request_target:
pull_request:
types:
- opened
- ready_for_review
@@ -22,8 +20,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules
id: cache
@@ -49,7 +45,7 @@ jobs:
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
@@ -64,7 +60,6 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
@@ -112,82 +107,65 @@ jobs:
echo "Branch slug: ${branch_slug}"
echo ">>>> eof debug"
projects_array=()
projects_e2e=""
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
preview_tools="not deployed"
# parse if affected is any of three main applications, if none - use all of them
if echo "$affected" | grep -q governance; then
echo "Governance is affected"
projects_array+=("governance")
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if echo "$affected" | grep -q trading; then
echo "Trading is affected"
projects_array+=("trading")
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if echo "$affected" | grep -q explorer; then
echo "Explorer is affected"
projects_array+=("explorer")
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
if [[ ${#projects_array[@]} -eq 0 ]]; then
projects_array=("governance" "trading" "explorer")
if [[ -z "$projects_e2e" ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
# applications parsed before this loop are applicable for running e2e-tests
projects_e2e_array=()
for project in "${projects_array[@]}"; do
projects_e2e_array+=("${project}-e2e")
done
# all applications below this loop are not applicable for running e2e-test
# check if pull request event to deploy tools
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects_array+=("multisig-signer")
projects+=' "multisig-signer" '
fi
# those apps deploy only from develop to mainnet
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on s3"
projects_array+=("multisig-signer")
projects+=' "multisig-signer" '
fi
if echo "$affected" | grep -q static; then
echo "static is affected"
echo "Deploying static on s3"
projects_array+=("static")
projects+=' "static" '
fi
if echo "$affected" | grep -q ui-toolkit; then
echo "ui-toolkit is affected"
echo "Deploying ui-toolkit on s3"
projects_array+=("ui-toolkit")
projects+=' "ui-toolkit" '
fi
fi
echo "Projects: ${projects_array[@]}"
echo "Projects E2E: ${projects_e2e_array[@]}"
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
projects_e2e_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_e2e_array[@]}")
echo PROJECTS_E2E=$projects_e2e_json >> $GITHUB_ENV
echo PROJECTS=$projects_json >> $GITHUB_ENV
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
projects=[${projects// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$projects >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
@@ -204,7 +182,7 @@ jobs:
cypress:
needs: lint-test-build
name: '(CI) cypress'
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
@@ -214,7 +192,7 @@ jobs:
publish-dist:
needs: lint-test-build
name: '(CD) publish dist'
# if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
@@ -225,7 +203,7 @@ jobs:
needs:
- publish-dist
- lint-test-build
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
timeout-minutes: 60
name: '(CD) comment preview links'
steps:
-1
View File
@@ -33,7 +33,6 @@ jobs:
with:
fetch-depth: 0
path: './frontend-monorepo'
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
-2
View File
@@ -11,8 +11,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
+4 -6
View File
@@ -19,8 +19,6 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Set up QEMU
id: quemu
@@ -33,7 +31,7 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
uses: docker/login-action@v2
with:
registry: ghcr.io
@@ -145,7 +143,7 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image
@@ -160,7 +158,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: ghcr-push
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -230,7 +228,7 @@ jobs:
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
@@ -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, ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community';
type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
@@ -31,58 +31,6 @@ 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}
@@ -98,11 +46,60 @@ 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,9 +1,8 @@
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 type { ColDef } from 'ag-grid-community';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
@@ -40,34 +39,54 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
};
}, []);
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: ({
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={({
data,
}: VegaValueGetterParams<MarketFieldsFragment>) => {
return data?.state ? MarketStateMapping[data?.state] : '-';
},
},
{
colId: 'asset',
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
hide: window.innerWidth <= BREAKPOINT_MD,
cellRenderer: ({
}}
/>
<AgGridColumn
colId="asset"
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset.symbol"
hide={window.innerWidth <= BREAKPOINT_MD}
cellRenderer={({
data,
}: VegaICellRendererParams<
MarketFieldsFragment,
@@ -86,19 +105,19 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
) : (
''
);
},
},
{
flex: 2,
headerName: t('Market ID'),
field: 'id',
hide: window.innerWidth <= BREAKPOINT_MD,
},
{
colId: 'actions',
headerName: '',
field: 'id',
cellRenderer: ({
}}
/>
<AgGridColumn
flex={2}
headerName={t('Market ID')}
field="id"
hide={window.innerWidth <= BREAKPOINT_MD}
/>
<AgGridColumn
colId="actions"
headerName=""
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? (
@@ -107,34 +126,9 @@ 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>
);
};
@@ -1,6 +1,7 @@
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 {
@@ -8,7 +9,7 @@ import type {
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
import type { RowClickedEvent } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
@@ -63,128 +64,7 @@ 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
@@ -203,7 +83,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if (
@@ -215,7 +94,128 @@ 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 })}
+3 -2
View File
@@ -1,5 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
@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';
/* You can add global styles to this file, and also import other style files */
@tailwind base;
+6
View File
@@ -0,0 +1,6 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;
+3 -2
View File
@@ -1,5 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
@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';
@tailwind base;
@tailwind components;
@@ -21,14 +21,11 @@ import {
HealthBar,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
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 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 { Grid } from '../../grid';
import { HealthDialog } from '../../health-dialog';
@@ -42,234 +39,6 @@ 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}>
@@ -295,11 +64,258 @@ 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={() => {
@@ -1,6 +1,7 @@
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { AgGridColumn } from 'ag-grid-react';
import type { GetRowIdParams, ColDef } from 'ag-grid-community';
import type { GetRowIdParams } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n';
import type {
@@ -35,75 +36,6 @@ 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
@@ -117,9 +49,74 @@ 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,4 +1,5 @@
import { useRef, useCallback, useEffect } from 'react';
import type { ReactNode } from 'react';
import { AgGridReact } from 'ag-grid-react';
import type {
AgGridReactProps,
@@ -6,17 +7,18 @@ import type {
AgGridReact as AgGridReactType,
} from 'ag-grid-react';
import classNames from 'classnames';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
import './grid.scss';
type Props = (AgGridReactProps | AgReactUiProps) & {
isRowClickable?: boolean;
style?: React.CSSProperties;
children: ReactNode;
};
export const Grid = ({ isRowClickable, ...props }: Props) => {
export const Grid = ({ isRowClickable, children, ...props }: Props) => {
const gridRef = useRef<AgGridReactType | null>(null);
const resizeGrid = useCallback(() => {
@@ -42,6 +44,8 @@ export const Grid = ({ isRowClickable, ...props }: Props) => {
onGridReady={handleOnGridReady}
suppressRowClickSelection
{...props}
/>
>
{children}
</AgGridReact>
);
};
-1
View File
@@ -14,7 +14,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_SENTRY_DSN=https://dummy@o999999.ingest.sentry.io/9999999
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
@@ -90,7 +90,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('All').click();
cy.get(`[row-id="${partiallyFilledId}"]`)
.eq(0)
.eq(1)
.within(() => {
cy.get(`[col-id='${orderStatus}']`).should(
'have.text',
@@ -1,4 +1,5 @@
import { checkSorting, aliasGQLQuery } from '@vegaprotocol/cypress';
import { checkSorting } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsDataQuery } from '@vegaprotocol/mock';
import { positionsQuery } from '@vegaprotocol/mock';
@@ -14,12 +15,13 @@ const toastContent = 'toast-content';
const tooltipContent = 'tooltip-content';
// #endregion
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
it('renders positions on trading page', () => {
visitAndClickPositions();
// 7004-POSI-001
@@ -62,14 +64,7 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
);
});
});
describe('positions', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
it('rows should be displayed despite errors', () => {
const errors = [
{
@@ -108,7 +103,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.get(
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
)
.eq(0)
.eq(1)
.within(() => {
emptyCells.forEach((cell) => {
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
@@ -169,9 +164,8 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
);
});
// let elementWidth: number;
it('Resize column', () => {
let elementWidth: number;
visitAndClickPositions();
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
cy.get('.ag-header-container').within(() => {
@@ -186,33 +180,29 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.get(`[col-id="marketName"]`)
.invoke('width')
.should('be.greaterThan', 250);
});
cy.get(`[col-id="marketName"]`)
.invoke('width')
.then((width) => {
elementWidth = width as number;
})
.then(() => {
let localStorageCopy: Record<string, string>;
cy.window().then((win) => {
localStorageCopy = { ...win.localStorage };
});
// This test depends on the previous one
it('Has persisted column widths', () => {
const width = 400;
cy.reload();
cy.window().then((win) => {
Object.keys(localStorageCopy).forEach((key) => {
win.localStorage.setItem(key, localStorageCopy[key]);
});
});
cy.window().then((win) => {
win.localStorage.setItem(
'vega_positions_store',
JSON.stringify({
state: {
gridStore: {
columnState: [{ colId: 'marketName', width }],
},
},
})
);
});
visitAndClickPositions();
// 7004-POSI-012
cy.get('.ag-center-cols-container .ag-row')
.first()
.find('[col-id="marketName"]')
.invoke('outerWidth')
.should('equal', width);
// 7004-POSI-012
cy.get('[col-id="marketName"]')
.invoke('width')
.should('equal', elementWidth);
});
});
it('Scroll horizontally', () => {
@@ -301,7 +291,6 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.getByTestId(dialogContent).should('be.visible');
});
});
function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions')
@@ -337,7 +326,6 @@ function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
}
function assertPNLColor(
pnlSelector: string,
positiveClass: string,
@@ -359,7 +347,6 @@ function assertPNLColor(
}
});
}
function visitAndClickPositions() {
cy.visit('/#/markets/market-0');
cy.getByTestId(positions).click();
+1 -1
View File
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
+1 -1
View File
@@ -1,7 +1,7 @@
NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a
NX_ETHERSCAN_URL=https://etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
+1 -1
View File
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
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
+1 -1
View File
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
+1 -1
View File
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks
@@ -1,5 +1,7 @@
import {
matchFilter,
liquidityProvisionsDataProvider,
LiquidityTable,
lpAggregatedDataProvider,
useCheckLiquidityStatus,
} from '@vegaprotocol/liquidity';
@@ -22,16 +24,18 @@ import {
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useEffect, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react';
import type { Filter } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarket, useStaticMarketData } from '@vegaprotocol/markets';
import { DocsLinks } from '@vegaprotocol/environment';
import { LiquidityContainer } from '../../components/liquidity-container';
const enum LiquidityTabs {
Active = 'active',
@@ -45,6 +49,65 @@ export const Liquidity = () => {
return <LiquidityViewContainer marketId={marketId} />;
};
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: { marketId: marketId || '' },
update: () => true,
skip: !marketId,
});
useEffect(() => {
const interval = setInterval(reload, 30000);
return () => clearInterval(interval);
}, [reload]);
};
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const quantum =
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
return (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
quantum={quantum}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
/>
</div>
);
};
const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
const { data: market } = useMarket(marketId);
const { data: marketData } = useStaticMarketData(marketId);
@@ -19,7 +19,10 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import { HeaderTitle } from '../../components/header';
import {
@@ -46,6 +49,7 @@ const MarketBottomPanel = memo(
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
const { screenSize } = useScreenDimensions();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
return 'xxxl' === screenSize ? (
<ResizableGrid
@@ -65,6 +69,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -73,6 +81,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -81,12 +93,22 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('All')}>
<VegaWalletContainer>
<TradingViews.orders.component marketId={marketId} />
<TradingViews.orders.component
marketId={marketId}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
@@ -94,6 +116,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -111,6 +134,8 @@ const MarketBottomPanel = memo(
<VegaWalletContainer>
<TradingViews.positions.component
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -120,6 +145,7 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -132,7 +158,10 @@ const MarketBottomPanel = memo(
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.positions.component onMarketClick={onMarketClick} />
<TradingViews.positions.component
onMarketClick={onMarketClick}
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
<Tab id="open-orders" name={t('Open')}>
@@ -140,6 +169,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -148,6 +181,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -156,12 +193,22 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectedOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('All')}>
<VegaWalletContainer>
<TradingViews.orders.component marketId={marketId} />
<TradingViews.orders.component
marketId={marketId}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
@@ -169,6 +216,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -178,6 +226,7 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -1,19 +1,18 @@
import type { ComponentProps } from 'react';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import { MarketInfoAccordionContainer } from '@vegaprotocol/markets';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { OrderListContainer, Filter } from '@vegaprotocol/orders';
import type { OrderListContainerProps } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills';
import { PositionsContainer } from '@vegaprotocol/positions';
import { TradesContainer } from '@vegaprotocol/trades';
import type { ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { Filter } from '@vegaprotocol/orders';
import { NO_MARKET } from './constants';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { AccountsContainer } from '../../components/accounts-container';
import { LiquidityContainer } from '../../components/liquidity-container';
import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container';
import { NO_MARKET } from './constants';
import { LiquidityContainer } from '../liquidity/liquidity';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -66,25 +65,25 @@ export const TradingViews = {
positions: { label: 'Positions', component: PositionsContainer },
activeOrders: {
label: 'Active',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Open} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Open} />
),
},
closedOrders: {
label: 'Closed',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Closed} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Closed} />
),
},
rejectedOrders: {
label: 'Rejected',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Rejected} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Rejected} />
),
},
orders: {
label: 'All',
component: OrdersContainer,
component: OrderListContainer,
},
collateral: { label: 'Collateral', component: AccountsContainer },
fills: { label: 'Fills', component: FillsContainer },
@@ -313,6 +313,7 @@ const ClosedMarketsDataGrid = ({
minWidth: 100,
}}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
storeKey="closedMarkets"
/>
);
};
@@ -2,6 +2,7 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
@@ -16,13 +17,17 @@ export const DepositsContainer = () => {
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
return (
<div className="h-full">
<DepositsTable
rowData={data}
ref={gridRef}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
<div className="h-full relative">
<DepositsTable
rowData={data}
ref={gridRef}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
@@ -1,26 +1,29 @@
import { useEffect } from 'react';
import type { ReactNode } from 'react';
import { LayoutPriority } from 'allotment';
import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { usePaneLayout } from '@vegaprotocol/react-helpers';
import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/orders';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { AccountsContainer } from '../../components/accounts-container';
import { DepositsContainer } from './deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { WithdrawalsContainer } from './withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import { FillsContainer } from '@vegaprotocol/fills';
import type { ReactNode } from 'react';
import { useEffect } from 'react';
import { usePaneLayout } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import { LedgerContainer } from '../../components/ledger-container';
import { DepositsContainer } from './deposits-container';
import { LayoutPriority } from 'allotment';
import { usePageTitleStore } from '../../stores';
import { LedgerContainer } from '@vegaprotocol/ledger';
import { AccountsContainer } from '../../components/accounts-container';
import { AccountHistoryContainer } from './account-history-container';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import {
ResizableGrid,
ResizableGridPanel,
} from '../../components/resizable-grid';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -44,6 +47,7 @@ export const Portfolio = () => {
}, [updateTitle]);
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'h-full max-h-full flex flex-col';
return (
@@ -59,17 +63,29 @@ export const Portfolio = () => {
</Tab>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<PositionsContainer onMarketClick={onMarketClick} allKeys />
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="portfolioPositions"
allKeys
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<OrdersContainer />
<OrderListContainer
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
storeKey="portfolioOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<FillsContainer onMarketClick={onMarketClick} />
<FillsContainer
onMarketClick={onMarketClick}
storeKey="portfolioFills"
/>
</VegaWalletContainer>
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
@@ -89,7 +105,10 @@ export const Portfolio = () => {
<Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer>
<AccountsContainer />
<AccountsContainer
storeKey="portfolioCollateral"
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="deposits" name={t('Deposits')}>
@@ -8,19 +8,16 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
storeKey,
onMarketClick,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
storeKey?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
@@ -29,12 +26,6 @@ export const AccountsContainer = ({
const openDepositDialog = useDepositDialog((store) => store.open);
const openTransferDialog = useTransferDialog((store) => store.open);
const gridStore = useAccountStore((store) => store.gridStore);
const updateGridStore = useAccountStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
const onClickAsset = useCallback(
(assetId?: string) => {
assetId && openAssetDetailsDialog(assetId);
@@ -60,7 +51,7 @@ export const AccountsContainer = ({
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
storeKey={storeKey}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
@@ -84,9 +75,3 @@ export const AccountsContainer = ({
</div>
);
};
const useAccountStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_accounts_store',
})
);
@@ -1,49 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from '@vegaprotocol/fills';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
export const FillsContainer = ({
marketId,
onMarketClick,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey } = useVegaWallet();
const gridStore = useFillsStore((store) => store.gridStore);
const updateGridStore = useFillsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return (
<FillsManager
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
gridProps={gridStoreCallbacks}
/>
);
};
const useFillsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_fills_store',
})
);
@@ -1 +0,0 @@
export * from './fills-container';
@@ -1 +0,0 @@
export * from './ledger-container';
@@ -1,36 +0,0 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { LedgerManager } from '@vegaprotocol/ledger';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const LedgerContainer = () => {
const { pubKey } = useVegaWallet();
const gridStore = useLedgerStore((store) => store.gridStore);
const updateGridStore = useLedgerStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <LedgerManager partyId={pubKey} gridProps={gridStoreCallbacks} />;
};
const useLedgerStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_ledger_store',
})
);
@@ -1 +0,0 @@
export * from './liquidity-container';
@@ -1,93 +0,0 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import {
lpAggregatedDataProvider,
type Filter,
LiquidityTable,
liquidityProvisionsDataProvider,
} from '@vegaprotocol/liquidity';
import { useMarket } from '@vegaprotocol/markets';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import type { AgGridReact } from 'ag-grid-react';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { useEffect, useRef } from 'react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const gridStore = useLiquidityStore((store) => store.gridStore);
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
const { data: market } = useMarket(marketId);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const quantum =
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
return (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
quantum={quantum}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
{...gridStoreCallbacks}
/>
</div>
);
};
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: { marketId: marketId || '' },
update: () => true,
skip: !marketId,
});
useEffect(() => {
const interval = setInterval(reload, 30000);
return () => clearInterval(interval);
}, [reload]);
};
const useLiquidityStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_ledger_store',
})
);
@@ -1 +0,0 @@
export * from './orders-container';
@@ -1,106 +0,0 @@
import { act, renderHook } from '@testing-library/react';
import {
FilterStatusValue,
STORAGE_KEY,
useOrderListGridState,
} from './orders-container';
import { Filter } from '@vegaprotocol/orders';
import { OrderType } from '@vegaprotocol/types';
describe('useOrderListGridState', () => {
afterAll(() => {
localStorage.clear();
});
const setup = (filter: Filter | undefined) => {
return renderHook(() => useOrderListGridState(filter));
};
it.each(Object.values(Filter))(
'providers correct AgGrid filter for %s',
(filter) => {
const { result } = setup(filter);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
status: {
value: FilterStatusValue[filter],
},
},
});
}
);
it('provides correct AgGrid filter for all', () => {
const { result } = setup(undefined);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: undefined,
});
});
it.each(Object.values(Filter))(
'sets and stores column state and filters for %s',
(filter) => {
const filterModel = {
type: {
value: [OrderType.TYPE_LIMIT],
},
};
const { result } = setup(filter);
act(() => {
result.current.updateGridState(filter, {
filterModel,
});
});
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
});
const columnState = [{ colId: 'status', width: 200 }];
act(() => {
result.current.updateGridState(filter, {
columnState,
});
});
expect(result.current.gridState).toEqual({
columnState,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
});
const storeKeyMap = {
[Filter.Open]: 'open',
[Filter.Rejected]: 'rejected',
[Filter.Closed]: 'closed',
};
expect(JSON.parse(localStorage.getItem(STORAGE_KEY) || '')).toMatchObject(
{
state: {
[storeKeyMap[filter]]: {
columnState,
filterModel, // no need to check that status is set, hook will return status
},
},
}
);
}
);
});
@@ -1,166 +0,0 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { Filter } from '@vegaprotocol/orders';
import { OrderListManager } from '@vegaprotocol/orders';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { OrderStatus } from '@vegaprotocol/types';
export const FilterStatusValue = {
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
[Filter.Closed]: [
OrderStatus.STATUS_CANCELLED,
OrderStatus.STATUS_EXPIRED,
OrderStatus.STATUS_FILLED,
OrderStatus.STATUS_PARTIALLY_FILLED,
OrderStatus.STATUS_STOPPED,
],
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
};
export interface OrderContainerProps {
marketId?: string;
filter?: Filter;
}
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const { gridState, updateGridState } = useOrderListGridState(filter);
const gridStoreCallbacks = useDataGridEvents(gridState, (newState) => {
updateGridState(filter, newState);
});
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<OrderListManager
partyId={pubKey}
marketId={marketId}
filter={filter}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
/>
);
};
export const STORAGE_KEY = 'vega_order_list_store';
const useOrderListStore = create<{
open: DataGridStore;
closed: DataGridStore;
rejected: DataGridStore;
all: DataGridStore;
update: (filter: Filter | undefined, gridStore: DataGridStore) => void;
}>()(
persist(
(set) => ({
open: {},
closed: {},
rejected: {},
all: {},
update: (filter, newStore) => {
switch (filter) {
case Filter.Open: {
set((curr) => ({
open: {
...curr.open,
...newStore,
},
}));
return;
}
case Filter.Closed: {
set((curr) => ({
closed: {
...curr.closed,
...newStore,
},
}));
return;
}
case Filter.Rejected: {
set((curr) => ({
rejected: {
...curr.rejected,
...newStore,
},
}));
return;
}
case undefined: {
set((curr) => ({
all: {
...curr.all,
...newStore,
},
}));
return;
}
}
},
}),
{
name: STORAGE_KEY,
}
)
);
export const useOrderListGridState = (filter: Filter | undefined) => {
const updateGridState = useOrderListStore((store) => store.update);
const gridState = useOrderListStore((store) => {
// Return the column/filter state for the given filter but ensuring that
// each filter controlled by the tab is always applied
switch (filter) {
case Filter.Open: {
return {
columnState: store.open.columnState,
filterModel: {
...store.open.filterModel,
status: {
value: FilterStatusValue[Filter.Open],
},
},
};
}
case Filter.Closed: {
return {
columnState: store.closed.columnState,
filterModel: {
...store.closed.filterModel,
status: {
value: FilterStatusValue[Filter.Closed],
},
},
};
}
case Filter.Rejected: {
return {
columnState: store.rejected.columnState,
filterModel: {
...store.rejected.filterModel,
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
};
}
default: {
return store.all;
}
}
});
return { gridState, updateGridState };
};
@@ -1 +0,0 @@
export * from './positions-container';
@@ -1,25 +1,17 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render, screen, act } from '@testing-library/react';
import { TelemetryApproval } from './telemetry-approval';
jest.mock('@vegaprotocol/logger', () => ({
SentryInit: () => undefined,
SentryClose: () => undefined,
}));
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
}));
describe('TelemetryApproval', () => {
it('click on checkbox should be properly handled', async () => {
it('click on checkbox should be properly handled', () => {
const helpText = 'My help text';
render(<TelemetryApproval helpText={helpText} />);
expect(screen.getByRole('checkbox')).toHaveAttribute(
'data-state',
'unchecked'
);
await userEvent.click(screen.getByRole('checkbox'));
act(() => {
screen.getByRole('checkbox').click();
});
expect(screen.getByRole('checkbox')).toHaveAttribute(
'data-state',
'checked'
@@ -11,7 +11,6 @@ import { WelcomeNoticeDialog } from './welcome-notice-dialog';
import { useGlobalStore } from '../../stores';
import { useEnvironment } from '@vegaprotocol/environment';
import { Networks } from '@vegaprotocol/environment';
import { isTestEnv } from '@vegaprotocol/utils';
export const WelcomeDialog = () => {
const { VEGA_ENV } = useEnvironment();
@@ -32,7 +31,9 @@ export const WelcomeDialog = () => {
);
const isRiskDialogNeeded =
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
riskAccepted !== 'true' &&
VEGA_ENV !== Networks.MAINNET &&
!('Cypress' in window);
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
+20
View File
@@ -0,0 +1,20 @@
const windowOrDefault = (key: string, defaultValue?: string) => {
if (typeof window !== 'undefined') {
if (window._env_ && window._env_[key]) {
return window._env_[key];
}
}
return defaultValue || '';
};
/**
* Need to have default value as next in-lines environment variables. Cannot figure out dynamic keys.
* So must provide the default with the key so that next can figure it out.
*/
export const ENV = {
envName: windowOrDefault('NX_VEGA_ENV', process.env['NX_VEGA_ENV']),
dsn: windowOrDefault(
'NX_TRADING_SENTRY_DSN',
process.env['NX_TRADING_SENTRY_DSN']
),
};
+1
View File
@@ -0,0 +1 @@
export * from './env';
@@ -12,26 +12,22 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
.fn()
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
}));
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
}));
describe('useTelemetryApproval', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('hook should return proper array', () => {
const { result } = renderHook(() => useTelemetryApproval());
expect(result.current[0]).toEqual(false);
expect(result.current[1]).toEqual(expect.any(Function));
const res = renderHook(() => useTelemetryApproval());
expect(res.result.current[0]).toEqual(false);
expect(res.result.current[1]).toEqual(expect.any(Function));
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
});
it('hook should init stuff properly', async () => {
const { result } = renderHook(() => useTelemetryApproval());
const res = renderHook(() => useTelemetryApproval());
await act(() => {
result.current[1](true);
res.result.current[1](true);
});
await waitFor(() => {
expect(SentryInit).toHaveBeenCalled();
@@ -40,9 +36,9 @@ describe('useTelemetryApproval', () => {
});
it('hook should close stuff properly', async () => {
const { result } = renderHook(() => useTelemetryApproval());
const res = renderHook(() => useTelemetryApproval());
await act(() => {
result.current[1](false);
res.result.current[1](false);
});
await waitFor(() => {
expect(SentryClose).toHaveBeenCalled();
@@ -1,25 +1,24 @@
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useCallback } from 'react';
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
import { useEnvironment } from '@vegaprotocol/environment';
import { ENV } from '../config';
export const STORAGE_KEY = 'vega_telemetry_approval';
export const useTelemetryApproval = (): [
value: boolean,
setValue: (value: boolean) => void
] => {
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
const setApprove = useCallback(
(value: boolean) => {
if (value && SENTRY_DSN) {
SentryInit(SENTRY_DSN, VEGA_ENV);
if (value) {
SentryInit(ENV.dsn, ENV.envName);
return setValue('1');
}
SentryClose();
removeValue();
},
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
[setValue, removeValue]
);
return [Boolean(value), setApprove];
};
+2 -2
View File
@@ -34,6 +34,7 @@ import { ViewingBanner } from '../components/viewing-banner';
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
import { AppLoader, DynamicLoader } from '../components/app-loader';
import { Navbar } from '../components/navbar';
import { ENV } from '../lib/config';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
@@ -154,11 +155,10 @@ const PartyData = () => {
};
const MaybeConnectEagerly = () => {
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
useVegaEagerConnect(Connectors);
const [isTelemetryApproved] = useTelemetryApproval();
useEthereumEagerConnect(
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
isTelemetryApproved ? { dsn: ENV.dsn, env: ENV.envName } : {}
);
const { pubKey, connect } = useVegaWallet();
+4
View File
@@ -20,6 +20,10 @@ export default function Document() {
href="https://static.vega.xyz/favicon.ico"
/>
<script src="/theme-setter.js" type="text/javascript" async />
{['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
/* eslint-disable-next-line @next/next/no-sync-scripts */
<script src="/assets/env-config.js" type="text/javascript" />
) : null}
</Head>
<body className="font-alpha dark:bg-black dark:text-white">
<Main />
+3 -2
View File
@@ -1,5 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
@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';
@tailwind base;
@tailwind components;
@@ -1,25 +0,0 @@
import type { ColumnState } from 'ag-grid-community';
import type { StateCreator } from 'zustand';
export type DataGridStore = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
filterModel?: { [key: string]: any };
columnState?: ColumnState[];
};
export type DataGridSlice = {
gridStore: DataGridStore;
updateGridStore: (gridStore: DataGridStore) => void;
};
export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
gridStore: {},
updateGridStore: (newStore) => {
set((curr) => ({
gridStore: {
...curr.gridStore,
...newStore,
},
}));
},
});
@@ -7,7 +7,6 @@ import {
} from '@testing-library/react';
import * as helpers from '@vegaprotocol/data-provider';
import { AccountManager } from './accounts-manager';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
const mockedUseDataProvider = jest.fn();
jest.mock('@vegaprotocol/data-provider', () => ({
@@ -15,13 +14,6 @@ jest.mock('@vegaprotocol/data-provider', () => ({
useDataProvider: jest.fn(() => mockedUseDataProvider()),
}));
const gridProps = {
onGridReady: jest.fn(),
onColumnResized: jest.fn(),
onFilterChanged: jest.fn(),
onSortChanged: jest.fn(),
} as unknown as ReturnType<typeof useDataGridEvents>;
describe('AccountManager', () => {
describe('when rerender', () => {
beforeEach(() => {
@@ -51,7 +43,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
expect(
@@ -64,7 +55,6 @@ describe('AccountManager', () => {
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
@@ -82,7 +72,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
rerenderer = rerender;
@@ -96,7 +85,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
@@ -122,7 +110,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
+4 -4
View File
@@ -11,7 +11,6 @@ import type { PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import BreakdownTable from './breakdown-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
const AccountBreakdown = ({
assetId,
@@ -103,7 +102,7 @@ interface AccountManagerProps {
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
gridProps?: ReturnType<typeof useDataGridEvents>;
storeKey?: string;
}
export const AccountManager = ({
@@ -113,11 +112,12 @@ export const AccountManager = ({
partyId,
isReadOnly,
pinnedAsset,
storeKey,
onMarketClick,
gridProps,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables: { partyId },
@@ -144,8 +144,8 @@ export const AccountManager = ({
onClickBreakdown={setBreakdownAssetId}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
storeKey={storeKey}
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
{...gridProps}
/>
<AccountBreakdownDialog
assetId={breakdownAssetId}
+40 -57
View File
@@ -44,79 +44,62 @@ describe('AccountsTable', () => {
});
it('should apply correct formatting', async () => {
const { container } = render(
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
/>
);
await act(async () => {
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 = container.querySelector('.ag-center-cols-container');
expect(rows?.childElementCount).toBe(1);
const rows = await screen.findAllByRole('row');
expect(rows.length).toBe(6);
});
it('should apply correct formatting in view as user mode', async () => {
const { container } = render(
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={true}
/>
);
await act(async () => {
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 = container.querySelector('.ag-center-cols-container');
expect(rows?.childElementCount).toBe(1);
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 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 get correct account data', () => {
+19 -17
View File
@@ -17,7 +17,7 @@ import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
IGetRowsParams,
IRowNode,
RowNode,
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: IRowNode,
nodeB: IRowNode
nodeA: RowNode,
nodeB: RowNode
) => {
if (isNumeric(valueA) && isNumeric(valueB)) {
const a = toBigNum(valueA, nodeA.data.asset?.decimals);
@@ -73,6 +73,7 @@ export interface AccountTableProps extends AgGridReactProps {
onClickBreakdown?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
storeKey?: string;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -83,22 +84,20 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
onClickDeposit,
onClickBreakdown,
rowData,
isReadOnly,
pinnedAsset,
...props
},
ref
) => {
const pinnedRow = useMemo(() => {
if (!pinnedAsset) {
const pinnedAsset = useMemo(() => {
if (!props.pinnedAsset) {
return;
}
const currentPinnedAssetRow = rowData?.find(
(row) => row.asset.id === pinnedAsset?.id
(row) => row.asset.id === props.pinnedAsset?.id
);
if (!currentPinnedAssetRow) {
return {
asset: pinnedAsset,
asset: props.pinnedAsset,
available: '0',
used: '0',
total: '0',
@@ -106,7 +105,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
};
}
return currentPinnedAssetRow;
}, [pinnedAsset, rowData]);
}, [props.pinnedAsset, rowData]);
const { getRowHeight } = props;
@@ -114,17 +113,17 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(params: RowHeightParams) => {
if (
params.node.rowPinned &&
params.data.asset.id === pinnedAsset?.id &&
params.data.asset.id === props.pinnedAsset?.id &&
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
) {
return 32;
}
return getRowHeight ? getRowHeight(params) : undefined;
},
[pinnedAsset?.id, getRowHeight]
[props.pinnedAsset?.id, getRowHeight]
);
const showDepositButton = pinnedRow?.balance === '0';
const showDepositButton = pinnedAsset?.balance === '0';
const colDefs = useMemo(() => {
const defs: ColDef[] = [
@@ -268,7 +267,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
</CenteredGridCellWrapper>
);
}
return isReadOnly ? null : (
return props.isReadOnly ? null : (
<AccountsActionsDropdown
assetId={assetId}
assetContractAddress={
@@ -296,16 +295,19 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
onClickBreakdown,
onClickDeposit,
onClickWithdraw,
isReadOnly,
props.isReadOnly,
showDepositButton,
]);
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
const data = rowData?.filter(
(data) => data.asset.id !== props.pinnedAsset?.id
);
return (
<AgGrid
{...props}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No accounts')}
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
ref={ref}
tooltipShowDelay={500}
@@ -318,7 +320,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
}}
columnDefs={colDefs}
getRowHeight={getPinnedAssetRowHeight}
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
/>
);
}
+1 -9
View File
@@ -1,12 +1,4 @@
{
"presets": [
[
"@nrwl/next/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"presets": ["@nrwl/next/babel"],
"plugins": []
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -1
View File
@@ -8,7 +8,7 @@
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/announcements",
"outputPath": "dist/libs/accounts",
"tsConfig": "libs/announcements/tsconfig.lib.json",
"project": "libs/announcements/package.json",
"entryFile": "libs/announcements/src/index.ts",
+1 -1
View File
@@ -131,7 +131,7 @@ export const CandlesChartContainer = ({
return (
<div className="h-full flex flex-col">
<div className="px-4 py-2 flex flex-row flex-wrap gap-2">
<div className="px-4 py-2 flex flex-row flex-wrap gap-4">
<DropdownMenu
trigger={
<DropdownMenuTrigger>
+3 -1
View File
@@ -1,4 +1,5 @@
export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/ag-grid/use-column-sizes';
export * from './lib/column-definitions';
@@ -8,6 +9,7 @@ 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';
@@ -22,4 +24,4 @@ export * from './lib/type-helpers';
export * from './lib/cells/grid-progress-bar';
export * from './lib/ag-grid-update';
export * from './lib/use-datagrid-events';
export * from './lib/use-bottom-placeholder';
@@ -2,16 +2,23 @@ import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames';
export const AgGridThemed = ({
style,
gridRef,
storeKey,
...props
}: (AgGridReactProps | AgReactUiProps) & {
style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>;
storeKey?: string;
}) => {
const commonColumnCallbacks = useColumnSizes({
storeKey,
props,
});
const { theme } = useThemeSwitcher();
const defaultProps = {
rowHeight: 22,
@@ -29,7 +36,12 @@ export const AgGridThemed = ({
return (
<div className={wrapperClasses} style={style}>
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
<AgGridReact
{...defaultProps}
{...props}
{...commonColumnCallbacks}
ref={gridRef}
/>
</div>
);
};
@@ -4,6 +4,7 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
storeKey?: string;
};
export const AgGridLazyInternal = lazy(() =>
@@ -0,0 +1,122 @@
import type {
Column,
ColumnResizedEvent,
GridSizeChangedEvent,
GridReadyEvent,
} from 'ag-grid-community';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useColumnSizes } from './use-column-sizes';
import * as reactHelpers from '@vegaprotocol/react-helpers';
const mockApis = {
api: {
sizeColumnsToFit: jest.fn(),
},
columnApi: {
setColumnWidths: jest.fn(),
},
};
const mockValueSetter = jest.fn();
const mockStore = {
sizes: { testid: { col1: 100 } },
valueSetter: mockValueSetter,
};
jest.mock('zustand', () => ({
...jest.requireActual('zustand'),
create: () =>
jest.fn(() =>
jest.fn().mockImplementation((creator) => {
return creator(mockStore);
})
),
}));
describe('UseColumnSizes hook', () => {
const storeKey = 'testid';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return proper methods', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
expect(Object.keys(result.current)).toHaveLength(3);
expect(result.current).toStrictEqual({
onColumnResized: expect.any(Function),
onGridReady: expect.any(Function),
onGridSizeChanged: expect.any(Function),
});
});
it('onGridSizeChanged should call setSize', async () => {
jest
.spyOn(reactHelpers, 'useScreenDimensions')
.mockReturnValue({ screenSize: 'xxl' });
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await waitFor(() => {
expect(mockApis.columnApi.setColumnWidths).toHaveBeenCalledWith([
{ key: 'col1', newWidth: 100 },
]);
});
});
it('onColumnResized should fill up store', async () => {
const columns: Column[] = [
{ getColId: () => 'col1', getActualWidth: () => 100 },
{ getColId: () => 'col2', getActualWidth: () => 200 },
] as Column[];
const sizeObj = { col1: 100, col2: 200, clientWidth: 1000 };
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await act(() => {
result.current.onColumnResized?.({
columns,
finished: true,
source: 'uiColumnDragged',
...mockApis,
} as ColumnResizedEvent);
});
await waitFor(() => {
expect(mockValueSetter).toHaveBeenCalledWith(storeKey, sizeObj);
});
});
it('onGridReady should call setSizes', async () => {
const props = { onGridReady: jest.fn() };
const { result } = renderHook(() => useColumnSizes({ storeKey, props }));
const obTest = { cool: 1, ...mockApis };
await act(() => {
result.current.onGridReady?.(obTest as GridReadyEvent);
});
expect(props.onGridReady).toHaveBeenCalledWith(obTest);
expect(mockApis.api.sizeColumnsToFit).toHaveBeenCalledWith();
});
it('if no storeKey should be transparent', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey: '', props: {} })
);
expect(result.current).toStrictEqual({
onColumnResized: undefined,
onGridReady: undefined,
onGridSizeChanged: undefined,
});
});
});
@@ -0,0 +1,145 @@
import { useCallback, useRef } from 'react';
import type {
GridSizeChangedEvent,
GridReadyEvent,
ColumnResizedEvent,
} from 'ag-grid-community';
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
const STORAGE_KEY = 'vega_columns_sizes_store';
export const useColumnSizesStore = create<{
sizes: Record<string, Record<string, number>>;
valueSetter: (storeKey: string, value: Record<string, number>) => void;
}>()(
persist(
immer((set) => ({
sizes: {},
valueSetter: (storeKey, value) =>
set((state) => {
state.sizes[storeKey] = {
...(state.sizes[storeKey] || {}),
...value,
};
return state;
}),
})),
{ name: STORAGE_KEY }
)
);
interface UseColumnSizesProps {
props: AgGridReactProps | AgReactUiProps;
storeKey?: string;
}
export const useColumnSizes = ({
storeKey = '',
props,
}: UseColumnSizesProps): {
onColumnResized?: (event: ColumnResizedEvent) => void;
onGridReady?: (event: GridReadyEvent) => void;
onGridSizeChanged?: (event: GridSizeChangedEvent) => void;
} => {
const sizes = useColumnSizesStore((store) => store.sizes[storeKey] || {});
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
const widthRef = useRef(sizes['clientWidth'] || 0);
const {
onColumnResized: parentOnColumnResized,
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
} = props;
const recalculateSizes = useCallback((sizes: Record<string, number>) => {
if (
widthRef.current &&
sizes['clientWidth'] &&
widthRef.current !== sizes['clientWidth']
) {
const oldWidth = sizes['clientWidth'];
const ratio = widthRef.current / oldWidth;
return {
...Object.entries(sizes).reduce((agg, [key, value]) => {
agg[key] = value * ratio;
return agg;
}, {} as Record<string, number>),
width: widthRef.current,
} as Record<string, number>;
}
return sizes;
}, []);
const onColumnResized = useCallback(
(event: ColumnResizedEvent) => {
parentOnColumnResized?.(event);
if (
storeKey &&
event.source === 'uiColumnDragged' &&
event.finished &&
widthRef.current
) {
const { columns } = event;
if (columns?.length) {
const sizesObj = columns.reduce((aggr, column) => {
aggr[column.getColId()] = column.getActualWidth();
return aggr;
}, {} as Record<string, number>);
sizesObj['clientWidth'] = widthRef.current;
valueSetter(storeKey, sizesObj);
}
}
},
[valueSetter, storeKey, parentOnColumnResized]
);
const { screenSize } = useScreenDimensions();
const largeScreen = ['xl', 'xxl', 'xxxl'].includes(screenSize);
const setSizes = useCallback(
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
largeScreen && apiEvent?.api.sizeColumnsToFit();
} else {
const recalculatedSizes = recalculateSizes(sizes);
const newSizes = Object.entries(recalculatedSizes).map(
([key, size]) => ({
key,
newWidth: size,
})
);
apiEvent.columnApi.setColumnWidths(newSizes);
}
},
[storeKey, recalculateSizes, sizes, largeScreen]
);
const onGridReady = useCallback(
(event: GridReadyEvent) => {
parentOnGridReady?.(event);
setSizes(event);
},
[setSizes, parentOnGridReady]
);
const onGridSizeChanged = useCallback(
(event: GridSizeChangedEvent) => {
parentOnGridSizeChanged?.(event);
widthRef.current = event.clientWidth;
setSizes(event);
},
[parentOnGridSizeChanged, setSizes]
);
if (storeKey) {
return {
onGridReady,
onGridSizeChanged,
onColumnResized,
};
}
return {
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
onColumnResized: parentOnColumnResized,
};
};
@@ -1,10 +1,8 @@
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;
@@ -0,0 +1,49 @@
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,
});
});
});
+50
View File
@@ -0,0 +1,50 @@
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';
+1 -1
View File
@@ -7,6 +7,6 @@ export const COL_DEFS = {
minWidth: 45,
maxWidth: 45,
type: 'rightAligned',
pinned: 'right' as const,
pinned: 'right',
},
};
+8 -3
View File
@@ -4,17 +4,18 @@ import type {
ValueFormatterParams,
ValueGetterParams,
} from 'ag-grid-community';
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { IDatasource, IGetRowsParams, RowNode } 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'
'data' | 'value' | 'node'
> & {
data?: TRow;
value?: Get<TRow, TField>;
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
};
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
@@ -23,8 +24,12 @@ export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
TField
>;
export type VegaValueGetterParams<TRow> = Omit<ValueGetterParams, 'data'> & {
export type VegaValueGetterParams<TRow> = Omit<
ValueGetterParams,
'data' | 'node'
> & {
data?: TRow;
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
};
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
@@ -0,0 +1,68 @@
import type { RefObject } from 'react';
import { useCallback, useMemo } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
const ROW_ID = 'bottom-placeholder';
const fullWidthCellRenderer = () => null;
const isFullWidthRow = (params: IsFullWidthRowParams) =>
params.rowNode.data?.isLastPlaceholder;
interface Props {
gridRef: RefObject<AgGridReact>;
disabled?: boolean;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const useBottomPlaceholder = ({ gridRef, disabled }: Props) => {
const onBodyScrollEnd = useCallback(() => {
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
if (rowCont) {
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
if (lastRow && lastRow.data) {
const placeholderRow = {
...lastRow.data,
isLastPlaceholder: true,
id: ROW_ID,
};
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
? { update: [placeholderRow] }
: { add: [placeholderRow] };
gridRef.current?.api.applyTransaction(transaction);
}
}
}, [gridRef]);
const onRowsChanged = useCallback(() => {
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
if (placeholderNode) {
const transaction = {
remove: [placeholderNode.data],
};
gridRef.current?.api.applyTransaction(transaction);
}
onBodyScrollEnd();
}, [gridRef, onBodyScrollEnd]);
const getRowHeight = useCallback(
(params: RowHeightParams) =>
params.data?.isLastPlaceholder ? 50 : undefined,
[]
);
return useMemo(
() =>
!disabled
? {
onBodyScrollEnd,
rowClassRules: NO_HOVER_CSS_RULE,
isFullWidthRow,
fullWidthCellRenderer,
onSortChanged: onRowsChanged,
onFilterChanged: onRowsChanged,
getRowHeight,
}
: {},
[onBodyScrollEnd, onRowsChanged, disabled, getRowHeight]
);
};
@@ -1,185 +0,0 @@
import { act, render, waitFor } from '@testing-library/react';
import {
useDataGridEvents,
GRID_EVENT_DEBOUNCE_TIME,
} from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-lazy-themed';
import type { MutableRefObject } from 'react';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
const gridProps = {
rowData: [{ id: 1 }],
columnDefs: [
{
field: 'id',
width: 100,
resizable: true,
filter: 'agNumberColumnFilter',
},
],
style: { width: 500, height: 300 },
};
// Not using render hook so I can pass event callbacks
// to a rendered grid
function setup(...args: Parameters<typeof useDataGridEvents>) {
let gridRef;
function TestComponent() {
const hookCallbacks = useDataGridEvents(...args);
gridRef = useRef<AgGridReact | null>(null);
return <AgGridThemed gridRef={gridRef} {...gridProps} {...hookCallbacks} />;
}
render(<TestComponent />);
return gridRef as unknown as MutableRefObject<AgGridReact>;
}
describe('useDataGridEvents', () => {
const originalWarn = console.warn;
beforeAll(() => {
jest.useFakeTimers();
// disabling some ag grid warnings that are caused by test setup only
console.warn = () => undefined;
});
afterAll(() => {
jest.useRealTimers();
console.warn = originalWarn;
});
it('default state is set and callback is called on column or filter event', async () => {
const callback = jest.fn();
const initialState = {
filterModel: undefined,
columnState: undefined,
};
const result = setup(initialState, callback);
// column state was not updated, so the default width provided by the
// col def should be set
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
gridProps.columnDefs[0].width
);
// no filters set
expect(result.current.api.getFilterModel()).toEqual({});
const newWidth = 400;
// Set col width
await act(async () => {
result.current.columnApi.setColumnWidth('id', newWidth);
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: [expect.objectContaining({ colId: 'id', width: newWidth })],
filterModel: {},
});
callback.mockClear();
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
newWidth
);
// Set filter
await act(async () => {
result.current.columnApi.applyColumnState({
state: [{ colId: 'id', sort: 'asc' }],
applyOrder: true,
});
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: [expect.objectContaining({ colId: 'id', sort: 'asc' })],
filterModel: {},
});
callback.mockClear();
expect(result.current.columnApi.getColumnState()[0].sort).toEqual('asc');
// Set filter
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
await act(async () => {
result.current.api.setFilterModel({
id: idFilter,
});
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: expect.any(Object),
filterModel: {
id: idFilter,
},
});
callback.mockClear();
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
});
it('applies grid state on ready', async () => {
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
const colState = { colId: 'id', width: 300, sort: 'desc' as const };
const initialState = {
filterModel: {
id: idFilter,
},
columnState: [colState],
};
const result = setup(initialState, jest.fn());
await waitFor(() => {
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
expect(result.current.columnApi.getColumnState()[0]).toEqual(
expect.objectContaining(colState)
);
});
});
it('debounces events', async () => {
const callback = jest.fn();
const initialState = {
filterModel: undefined,
columnState: undefined,
};
const result = setup(initialState, callback);
const newWidth = 400;
// Set col width multiple times
await act(async () => {
result.current.columnApi.setColumnWidth('id', newWidth);
result.current.columnApi.setColumnWidth('id', newWidth);
result.current.columnApi.setColumnWidth('id', newWidth);
});
expect(callback).not.toHaveBeenCalled();
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledTimes(1);
});
});
@@ -1,66 +0,0 @@
import debounce from 'lodash/debounce';
import type {
ColumnResizedEvent,
ColumnState,
FilterChangedEvent,
GridReadyEvent,
SortChangedEvent,
} from 'ag-grid-community';
import { useCallback, useMemo } from 'react';
type State = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
filterModel?: { [key: string]: any };
columnState?: ColumnState[];
};
type Event = ColumnResizedEvent | FilterChangedEvent | SortChangedEvent;
export const GRID_EVENT_DEBOUNCE_TIME = 300;
export const useDataGridEvents = (
state: State,
callback: (data: State) => void
) => {
// This function can be called very frequently by the onColumnResized
// grid callback, so its memoized to only update after resizing is finished
const onGridChange = useMemo(
() =>
debounce(({ api, columnApi }: Event) => {
if (!api || !columnApi) return;
const columnState = columnApi.getColumnState();
const filterModel = api.getFilterModel();
callback({ columnState, filterModel });
}, GRID_EVENT_DEBOUNCE_TIME),
[callback]
);
// check if we have stored column states or filter models and apply if we do
const onGridReady = useCallback(
({ api, columnApi }: GridReadyEvent) => {
if (!api || !columnApi) return;
if (state.columnState) {
columnApi.applyColumnState({
state: state.columnState,
applyOrder: true,
});
} else {
// ensure columns fit available space if no widths are set
api.sizeColumnsToFit();
}
if (state.filterModel) {
api.setFilterModel(state.filterModel);
}
},
[state]
);
return {
onGridReady,
onColumnResized: onGridChange,
onFilterChanged: onGridChange,
onSortChanged: onGridChange,
};
};
@@ -124,7 +124,7 @@ export const DealTicket = ({
normalizedOrder.size,
market.positionDecimalPlaces
).multipliedBy(toBigNum(price, market.decimalPlaces)),
market.decimalPlaces
asset.decimals
);
}
return null;
@@ -133,6 +133,7 @@ export const DealTicket = ({
normalizedOrder?.size,
market.decimalPlaces,
market.positionDecimalPlaces,
asset.decimals,
]);
const feeEstimate = useEstimateFees(
+39 -42
View File
@@ -1,11 +1,11 @@
import { forwardRef, useMemo } from 'react';
import { forwardRef } from 'react';
import { AgGridColumn } from 'ag-grid-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,46 +21,53 @@ export const DepositsTable = forwardRef<
AgGridReact,
TypedDataAgGrid<DepositFieldsFragment>
>((props, ref) => {
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: 'Asset', field: 'asset.symbol' },
{
headerName: 'Amount',
field: 'amount',
valueFormatter: ({
return (
<AgGrid
ref={ref}
defaultColDef={{ resizable: true }}
style={{ width: '100%', height: '100%' }}
suppressCellFocus={true}
storeKey="depositTable"
{...props}
>
<AgGridColumn headerName="Asset" field="asset.symbol" />
<AgGridColumn
headerName="Amount"
field="amount"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => {
return isNumeric(value) && data
? addDecimalsFormatNumber(value, data.asset.decimals)
: '';
},
},
{
headerName: 'Created at',
field: 'createdTimestamp',
valueFormatter: ({
: null;
}}
/>
<AgGridColumn
headerName="Created at"
field="createdTimestamp"
valueFormatter={({
value,
}: VegaValueFormatterParams<
DepositFieldsFragment,
'createdTimestamp'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '';
},
},
{
headerName: 'Status',
field: 'status',
valueFormatter: ({
}}
/>
<AgGridColumn
headerName="Status"
field="status"
valueFormatter={({
value,
}: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => {
return value ? DepositStatusMapping[value] : '';
},
},
{
headerName: 'Tx hash',
field: 'txHash',
cellRenderer: ({
}}
/>
<AgGridColumn
headerName="Tx hash"
field="txHash"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
@@ -71,19 +78,9 @@ export const DepositsTable = forwardRef<
{truncateByChars(value)}
</EtherscanLink>
);
},
flex: 1,
},
],
[]
);
return (
<AgGrid
ref={ref}
defaultColDef={{ flex: 1 }}
columnDefs={columnDefs}
style={{ width: '100%', height: '100%' }}
{...props}
/>
}}
flex={1}
/>
</AgGrid>
);
});
@@ -301,25 +301,6 @@ describe('useEnvironment', () => {
expect(fetch).toHaveBeenCalledWith(configUrl);
});
it('uses env vars from window._env_ if set', async () => {
const url = 'http://foo.bar.com';
// @ts-ignore _env_ is declared in app
window._env_ = {
VEGA_URL: url,
};
const { result } = setup();
await act(async () => {
result.current.initialize();
});
expect(result.current.VEGA_URL).toBe(url);
// @ts-ignore delete _env_
delete window['_env_'];
});
it('sets error if environment is invalid', async () => {
const error = console.error;
console.error = noop;
+23 -96
View File
@@ -269,96 +269,34 @@ const testSubscription = (client: Client) => {
* here to appease the environment store interface
*/
function compileEnvVars() {
const VEGA_ENV = windowOrDefault(
'VEGA_ENV',
process.env['NX_VEGA_ENV']
) as Networks;
const VEGA_ENV = process.env['NX_VEGA_ENV'] as Networks;
const env: Environment = {
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
VEGA_URL: process.env['NX_VEGA_URL'],
VEGA_ENV,
VEGA_CONFIG_URL: windowOrDefault(
'VEGA_CONFIG_URL',
process.env['NX_VEGA_CONFIG_URL'] as string
),
VEGA_NETWORKS: parseNetworks(
windowOrDefault('VEGA_NETWORKS', process.env['NX_VEGA_NETWORKS'])
),
VEGA_WALLET_URL: windowOrDefault(
'VEGA_WALLET_URL',
process.env['NX_VEGA_WALLET_URL'] as string
),
HOSTED_WALLET_URL: windowOrDefault(
'HOSTED_WALLET_URL',
process.env['NX_HOSTED_WALLET_URL']
),
ETHERSCAN_URL: getEtherscanUrl(
VEGA_ENV,
windowOrDefault('ETHERSCAN_URL', process.env['NX_ETHERSCAN_URL'])
),
VEGA_CONFIG_URL: process.env['NX_VEGA_CONFIG_URL'] as string,
VEGA_NETWORKS: parseNetworks(process.env['NX_VEGA_NETWORKS']),
VEGA_WALLET_URL: process.env['NX_VEGA_WALLET_URL'] as string,
HOSTED_WALLET_URL: process.env['NX_HOSTED_WALLET_URL'],
ETHERSCAN_URL: getEtherscanUrl(VEGA_ENV, process.env['NX_ETHERSCAN_URL']),
ETHEREUM_PROVIDER_URL: getEthereumProviderUrl(
VEGA_ENV,
windowOrDefault(
'ETHEREUM_PROVIDER_URL',
process.env['NX_ETHEREUM_PROVIDER_URL']
)
process.env['NX_ETHEREUM_PROVIDER_URL']
),
ETH_LOCAL_PROVIDER_URL: windowOrDefault(
'ETH_LOCAL_PROVIDER_URL',
process.env['NX_ETH_LOCAL_PROVIDER_URL']
),
ETH_WALLET_MNEMONIC: windowOrDefault(
'ETH_WALLET_MNEMONIC',
process.env['NX_ETH_WALLET_MNEMONIC']
),
ORACLE_PROOFS_URL: windowOrDefault(
'ORACLE_PROOFS_URL',
process.env['NX_ORACLE_PROOFS_URL']
),
VEGA_DOCS_URL: windowOrDefault(
'VEGA_DOCS_URL',
process.env['NX_VEGA_DOCS_URL']
),
VEGA_CONSOLE_URL: windowOrDefault(
'VEGA_CONSOLE_URL',
process.env['NX_VEGA_CONSOLE_URL']
),
VEGA_EXPLORER_URL: windowOrDefault(
'VEGA_EXPLORER_URL',
process.env['NX_VEGA_EXPLORER_URL']
),
VEGA_TOKEN_URL: windowOrDefault(
'VEGA_TOKEN_URL',
process.env['NX_VEGA_TOKEN_URL']
),
GITHUB_FEEDBACK_URL: windowOrDefault(
'GITHUB_FEEDBACK_URL',
process.env['NX_GITHUB_FEEDBACK_URL']
),
MAINTENANCE_PAGE: parseBoolean(
windowOrDefault('MAINTENANCE_PAGE', process.env['NX_MAINTENANCE_PAGE'])
),
GIT_BRANCH: windowOrDefault(
'GIT_COMMIT_BRANCH',
process.env['GIT_COMMIT_BRANCH']
),
GIT_COMMIT_HASH: windowOrDefault(
'GIT_COMMIT_HASH',
process.env['GIT_COMMIT_HASH']
),
GIT_ORIGIN_URL: windowOrDefault(
'GIT_ORIGIN_URL',
process.env['GIT_ORIGIN_URL']
),
ANNOUNCEMENTS_CONFIG_URL: windowOrDefault(
'ANNOUNCEMENTS_CONFIG_URL',
process.env['NX_ANNOUNCEMENTS_CONFIG_URL']
),
VEGA_INCIDENT_URL: windowOrDefault(
'VEGA_INCIDENT_URL',
process.env['NX_VEGA_INCIDENT_URL']
),
APP_VERSION: windowOrDefault('APP_VERSION', process.env['NX_APP_VERSION']),
SENTRY_DSN: windowOrDefault('SENTRY_DSN', process.env['NX_SENTRY_DSN']),
ETH_LOCAL_PROVIDER_URL: process.env['NX_ETH_LOCAL_PROVIDER_URL'],
ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'],
ORACLE_PROOFS_URL: process.env['NX_ORACLE_PROOFS_URL'],
VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'],
VEGA_CONSOLE_URL: process.env['NX_VEGA_CONSOLE_URL'],
VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
GITHUB_FEEDBACK_URL: process.env['NX_GITHUB_FEEDBACK_URL'],
MAINTENANCE_PAGE: parseBoolean(process.env['NX_MAINTENANCE_PAGE']),
GIT_BRANCH: process.env['GIT_COMMIT_BRANCH'],
GIT_COMMIT_HASH: process.env['GIT_COMMIT_HASH'],
GIT_ORIGIN_URL: process.env['GIT_ORIGIN_URL'],
ANNOUNCEMENTS_CONFIG_URL: process.env['NX_ANNOUNCEMENTS_CONFIG_URL'],
VEGA_INCIDENT_URL: process.env['NX_VEGA_INCIDENT_URL'],
APP_VERSION: process.env['NX_APP_VERSION'],
};
return env;
@@ -403,14 +341,3 @@ function getEtherscanUrl(
? 'https://etherscan.io'
: 'https://sepolia.etherscan.io';
}
export function windowOrDefault(key: string, defaultValue?: string) {
if (typeof window !== 'undefined') {
// @ts-ignore avoid conflic in env
if (window._env_ && window._env_[key]) {
// @ts-ignore presence has been check above
return window._env_[key];
}
}
return defaultValue || undefined;
}
@@ -5,7 +5,6 @@ import { useEnvironment } from './use-environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { isTestEnv } from '@vegaprotocol/utils';
const POLL_INTERVAL = 1000;
const BLOCK_THRESHOLD = 3;
@@ -39,7 +38,7 @@ export const useNodeHealth = () => {
return;
}
if (!isTestEnv() && window.location.hostname !== 'localhost') {
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
startPolling(POLL_INTERVAL);
}
}, [error, startPolling, stopPolling]);
@@ -52,7 +52,6 @@ const schemaObject = {
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
VEGA_INCIDENT_URL: z.optional(z.string()),
APP_VERSION: z.optional(z.string()),
SENTRY_DSN: z.optional(z.string()),
};
// combine schema above with custom rule to ensure either
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './lib/fills-manager';
export * from './lib/fills-container';
export * from './lib/fills-data-provider';
export * from './lib/__generated__/Fills';
+33
View File
@@ -0,0 +1,33 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from './fills-manager';
export const FillsContainer = ({
marketId,
onMarketClick,
storeKey,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
storeKey?: string;
}) => {
const { pubKey } = useVegaWallet();
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return (
<FillsManager
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
storeKey={storeKey}
/>
);
};
+8 -4
View File
@@ -2,7 +2,7 @@ import type { AgGridReact } from 'ag-grid-react';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type * as Schema from '@vegaprotocol/types';
import { fillsWithMarketProvider } from './fills-data-provider';
@@ -11,14 +11,14 @@ interface FillsManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
gridProps: ReturnType<typeof useDataGridEvents>;
storeKey?: string;
}
export const FillsManager = ({
partyId,
marketId,
onMarketClick,
gridProps,
storeKey,
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
@@ -38,6 +38,9 @@ export const FillsManager = ({
},
variables: { filter },
});
const bottomPlaceholderProps = useBottomPlaceholder({
gridRef,
});
return (
<FillsTable
@@ -45,8 +48,9 @@ export const FillsManager = ({
rowData={data}
partyId={partyId}
onMarketClick={onMarketClick}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
{...gridProps}
/>
);
};
+76 -80
View File
@@ -1,10 +1,9 @@
import { useMemo } from 'react';
import type {
AgGridReact,
AgGridReactProps,
AgReactUiProps,
} from 'ag-grid-react';
import type { ITooltipParams, ColDef } from 'ag-grid-community';
import type { ITooltipParams } from 'ag-grid-community';
import {
addDecimal,
addDecimalsFormatNumber,
@@ -14,6 +13,7 @@ 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,
@@ -39,90 +39,14 @@ export type Role = typeof TAKER | typeof MAKER | '-';
export type Props = (AgGridReactProps | AgReactUiProps) & {
partyId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
storeKey?: string;
};
export const FillsTable = forwardRef<AgGridReact, Props>(
({ partyId, onMarketClick, ...props }, ref) => {
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';
},
[negativeClassNames]: ({ data }: { data: Trade }) => {
const partySide = getPartySide(data, partyId);
return partySide === 'seller';
},
},
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)) : '';
},
},
{
colId: 'fill-actions',
cellRenderer: ({ data }: VegaICellRendererParams<Trade, 'id'>) => {
if (!data) return null;
return (
<FillActionsDropdown
buyOrderId={data.buyOrder}
sellOrderId={data.sellOrder}
tradeId={data.id}
/>
);
},
...COL_DEFS.actions,
},
],
[onMarketClick, partyId]
);
return (
<AgGrid
ref={ref}
columnDefs={columnDefs}
overlayNoRowsTemplate={t('No fills')}
defaultColDef={{ resizable: true }}
style={{ width: '100%', height: '100%' }}
@@ -131,7 +55,79 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
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={{
[positiveClassNames]: ({ data }: { data: Trade }) => {
const partySide = getPartySide(data, partyId);
return partySide === 'buyer';
},
[negativeClassNames]: ({ data }: { data: Trade }) => {
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={({
value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '';
}}
/>
<AgGridColumn
colId="fill-actions"
{...COL_DEFS.actions}
cellRenderer={({ data }: VegaICellRendererParams<Trade, 'id'>) => {
if (!data) return null;
return (
<FillActionsDropdown
buyOrderId={data.buyOrder}
sellOrderId={data.sellOrder}
tradeId={data.id}
/>
);
}}
/>
</AgGrid>
);
}
);
+1
View File
@@ -1,2 +1,3 @@
export * from './lib/ledger-container';
export * from './lib/ledger-manager';
export * from './lib/__generated__/LedgerEntries';
+17
View File
@@ -0,0 +1,17 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { LedgerManager } from './ledger-manager';
export const LedgerContainer = () => {
const { pubKey } = useVegaWallet();
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <LedgerManager partyId={pubKey} />;
};
+7 -19
View File
@@ -10,7 +10,6 @@ import { LedgerTable } from './ledger-table';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type * as Types from '@vegaprotocol/types';
import { LedgerExportLink } from './ledger-export-link';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
export interface Filter {
vegaTime?: {
@@ -25,13 +24,7 @@ const defaultFilter = {
},
};
export const LedgerManager = ({
partyId,
gridProps,
}: {
partyId: string;
gridProps: ReturnType<typeof useDataGridEvents>;
}) => {
export const LedgerManager = ({ partyId }: { partyId: string }) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter>(defaultFilter);
@@ -40,7 +33,7 @@ export const LedgerManager = ({
partyId,
dateRange: filter?.vegaTime?.value,
pagination: {
first: 10,
first: 5000,
},
}),
[partyId, filter?.vegaTime?.value]
@@ -52,23 +45,18 @@ export const LedgerManager = ({
skip: !variables.partyId,
});
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
gridProps.onFilterChanged(event);
},
[gridProps]
);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
}, []);
return (
<div className="h-full relative">
<LedgerTable
ref={gridRef}
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
{...gridProps}
onFilterChanged={onFilterChanged}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
/>
{data && <LedgerExportLink entries={data} partyId={partyId} />}
</div>
+147 -142
View File
@@ -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, useMemo } from 'react';
import { forwardRef } from 'react';
import { formatRFC3339, subDays } from 'date-fns';
export const TransferTooltipCellComponent = ({
@@ -47,146 +47,9 @@ 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%' }}
style={{ width: '100%', height: 'calc(100% - 50px)' }}
ref={ref}
tooltipShowDelay={500}
defaultColDef={{
@@ -198,9 +61,151 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
buttons: ['reset'],
},
}}
columnDefs={columnDefs}
storeKey="ledgerTable"
suppressLoadingOverlay
suppressNoRowsOverlay
{...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>
);
}
);
+2 -1
View File
@@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No liquidity provisions')}
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
getRowId={({ data }) => data.id}
ref={ref}
tooltipShowDelay={500}
defaultColDef={{
@@ -197,6 +197,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
tooltipComponent: TooltipCellComponent,
sortable: true,
}}
storeKey="liquidityProvisionTable"
{...props}
columnDefs={colDefs}
/>
-2
View File
@@ -67,7 +67,6 @@ export const liquidityProviderFeeShareQuery = (
export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
{
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
party: {
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
accountsConnection: {
@@ -93,7 +92,6 @@ export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
__typename: 'LiquidityProvision',
},
{
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
party: {
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
accountsConnection: {
@@ -61,6 +61,7 @@ export const MarketListTable = forwardRef<
columnDefs={columnDefs}
suppressCellFocus
components={{ PriceFlashCell, MarketName }}
storeKey="allMarkets"
{...props}
/>
);
+1
View File
@@ -1,4 +1,5 @@
export * from './order-data-provider';
export * from './order-list';
export * from './order-list-manager';
export * from './order-list-container';
export * from './mocks/generate-orders';
@@ -0,0 +1,42 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Filter } from './order-list-manager';
import { OrderListManager } from './order-list-manager';
export interface OrderListContainerProps {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
enforceBottomPlaceholder?: boolean;
filter?: Filter;
storeKey?: string;
}
export const OrderListContainer = ({
marketId,
onMarketClick,
onOrderTypeClick,
enforceBottomPlaceholder,
filter,
storeKey,
}: OrderListContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<OrderListManager
partyId={pubKey}
marketId={marketId}
filter={filter}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
isReadOnly={isReadOnly}
enforceBottomPlaceholder={enforceBottomPlaceholder}
storeKey={storeKey}
/>
);
};
@@ -2,9 +2,11 @@ import { t } from '@vegaprotocol/i18n';
import { useCallback, useRef, useState } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
import { OrderListTable } from '../order-list/order-list';
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
import {
@@ -14,31 +16,59 @@ import {
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
import { OrderEditDialog } from '../order-list/order-edit-dialog';
import type { Order } from '../order-data-provider';
import { OrderStatus } from '@vegaprotocol/types';
export enum Filter {
'Open' = 'Open',
'Closed' = 'Closed',
'Rejected' = 'Rejected',
'Open',
'Closed',
'Rejected',
}
const FilterStatusValue = {
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
[Filter.Closed]: [
OrderStatus.STATUS_CANCELLED,
OrderStatus.STATUS_EXPIRED,
OrderStatus.STATUS_FILLED,
OrderStatus.STATUS_PARTIALLY_FILLED,
OrderStatus.STATUS_STOPPED,
],
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
};
export interface OrderListManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
enforceBottomPlaceholder?: boolean;
filter?: Filter;
gridProps?: ReturnType<typeof useDataGridEvents>;
storeKey?: string;
}
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
<Button
variant="primary"
size="sm"
onClick={onClick}
data-testid="cancelAll"
>
{t('Cancel all')}
</Button>
</div>
);
export const OrderListManager = ({
partyId,
marketId,
onMarketClick,
onOrderTypeClick,
isReadOnly,
enforceBottomPlaceholder,
filter,
gridProps,
storeKey,
}: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [editOrder, setEditOrder] = useState<Order | null>(null);
@@ -61,6 +91,14 @@ export const OrderListManager = ({
},
});
const {
onFilterChanged: bottomPlaceholderOnFilterChanged,
...bottomPlaceholderProps
} = useBottomPlaceholder({
gridRef,
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
});
const cancel = useCallback(
(order: Order) => {
if (!order.market) return;
@@ -74,6 +112,26 @@ export const OrderListManager = ({
[create]
);
const onGridReady = useCallback(
({ api }: GridReadyEvent) => {
if (filter !== undefined) {
api.setFilterModel({
status: {
value: FilterStatusValue[filter],
},
});
}
},
[filter]
);
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
bottomPlaceholderOnFilterChanged?.();
},
[bottomPlaceholderOnFilterChanged]
);
const cancelAll = useCallback(() => {
create({
orderCancellation: {},
@@ -84,17 +142,20 @@ export const OrderListManager = ({
<>
<div className="h-full relative">
<OrderListTable
rowData={data}
rowData={data as Order[]}
ref={gridRef}
filter={filter}
onGridReady={onGridReady}
onCancel={cancel}
onEdit={setEditOrder}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
onFilterChanged={onFilterChanged}
isReadOnly={isReadOnly}
storeKey={storeKey}
suppressAutoSize
overlayNoRowsTemplate={error ? error.message : t('No orders')}
{...gridProps}
{...bottomPlaceholderProps}
/>
</div>
{!isReadOnly && hasAmendableOrder && (
@@ -137,16 +198,3 @@ export const OrderListManager = ({
</>
);
};
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
<Button
variant="primary"
size="sm"
onClick={onClick}
data-testid="cancelAll"
>
{t('Cancel all')}
</Button>
</div>
);
@@ -39,6 +39,7 @@ export type OrderListTableProps = TypedDataAgGrid<Order> & {
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
filter?: Filter;
isReadOnly: boolean;
storeKey?: string;
};
export const OrderListTable = memo<
+1 -1
View File
@@ -1,6 +1,6 @@
export * from './lib/__generated__/Positions';
export * from './lib/positions-container';
export * from './lib/positions-data-providers';
export * from './lib/positions-table';
export * from './lib/positions-manager';
export * from './lib/use-market-margin';
export * from './lib/use-open-volume';
@@ -1,28 +1,21 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { PositionsManager } from '@vegaprotocol/positions';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { PositionsManager } from './positions-manager';
export const PositionsContainer = ({
onMarketClick,
noBottomPlaceholder,
storeKey,
allKeys,
}: {
onMarketClick?: (marketId: string) => void;
noBottomPlaceholder?: boolean;
storeKey?: string;
allKeys?: boolean;
}) => {
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
const gridStore = usePositionsStore((store) => store.gridStore);
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
if (!pubKey) {
return (
<Splash>
@@ -45,13 +38,8 @@ export const PositionsContainer = ({
partyIds={partyIds}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
noBottomPlaceholder={noBottomPlaceholder}
storeKey={storeKey}
/>
);
};
const usePositionsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_positions_store',
})
);
+5 -4
View File
@@ -8,21 +8,22 @@ import {
positionsMetricsProvider,
positionsMarketsProvider,
} from './positions-data-providers';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useVegaWallet } from '@vegaprotocol/wallet';
interface PositionsManagerProps {
partyIds: string[];
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
gridProps: ReturnType<typeof useDataGridEvents>;
noBottomPlaceholder?: boolean;
storeKey?: string;
}
export const PositionsManager = ({
partyIds,
onMarketClick,
isReadOnly,
gridProps,
noBottomPlaceholder,
storeKey,
}: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create);
@@ -73,9 +74,9 @@ export const PositionsManager = ({
onMarketClick={onMarketClick}
onClose={onClose}
isReadOnly={isReadOnly}
storeKey={storeKey}
multipleKeys={partyIds.length > 1}
overlayNoRowsTemplate={error ? error.message : t('No positions')}
{...gridProps}
/>
</div>
);
@@ -48,6 +48,7 @@ interface Props extends TypedDataAgGrid<Position> {
onMarketClick?: (id: string, metaKey?: boolean) => void;
style?: CSSProperties;
isReadOnly: boolean;
storeKey?: string;
multipleKeys?: boolean;
pubKeys?: VegaWalletContextShape['pubKeys'];
pubKey?: VegaWalletContextShape['pubKey'];
@@ -38,6 +38,7 @@ export const ProposalsList = () => {
columnDefs={columnDefs}
rowData={filteredData}
defaultColDef={defaultColDef}
storeKey="proposedMarkets"
getRowId={({ data }) => data.id}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
@@ -1,6 +1,6 @@
import { useMemo, useEffect } from 'react';
import * as Schema from '@vegaprotocol/types';
import { removePaginationWrapper, isTestEnv } from '@vegaprotocol/utils';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals';
export const useNextProtocolUpgradeProposals = (since?: number) => {
@@ -21,7 +21,7 @@ export const useNextProtocolUpgradeProposals = (since?: number) => {
return;
}
if (!isTestEnv() && window.location.hostname !== 'localhost') {
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
startPolling(5000);
}
}, [error, startPolling, stopPolling]);
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import { useBlockStatisticsQuery } from './__generated__/BlockStatistics';
import sum from 'lodash/sum';
import { isTestEnv } from '@vegaprotocol/utils';
const DEFAULT_POLLS = 10;
const INTERVAL = 1000;
@@ -21,7 +20,7 @@ const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
return;
}
if (!isTestEnv() && window.location.hostname !== 'localhost') {
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
startPolling(INTERVAL);
}
}, [error, startPolling, stopPolling]);
+82 -87
View File
@@ -1,6 +1,5 @@
import type { AgGridReact } from 'ag-grid-react';
import { useMemo } from 'react';
import type { ColDef } from 'ag-grid-community';
import { AgGridColumn } from 'ag-grid-react';
import { forwardRef } from 'react';
import type {
VegaICellRendererParams,
@@ -48,90 +47,86 @@ interface Props extends AgGridReactProps {
onClick?: (price?: string) => void;
}
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,
}: 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,
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
getRowId={({ data }) => data.id}
ref={ref}
defaultColDef={{
flex: 1,
resizable: true,
}}
{...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>
);
}}
columnDefs={columnDefs}
{...props}
/>
);
}
);
<AgGridColumn
headerName={t('Size')}
field="size"
width={125}
type="rightAligned"
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Trade, 'size'>) => {
if (!value || !data?.market) {
return null;
}
return addDecimalsFormatNumber(
value,
data.market.positionDecimalPlaces
);
}}
cellRenderer={NumericCell}
/>
<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
View File
@@ -14,4 +14,3 @@ export * from './lib/remove-pagination-wrapper';
export * from './lib/time';
export * from './lib/validate';
export * from './lib/resolve-network-name';
export * from './lib/is-test-env';
-3
View File
@@ -1,3 +0,0 @@
export const isTestEnv = () => {
return window && 'Cypress' in window;
};

Some files were not shown because too many files have changed in this diff Show More