Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c92884768a | ||
|
|
4e78f1139c | ||
|
|
a3a2ec58b1 | ||
|
|
b32786d936 | ||
|
|
a64737a053 | ||
|
|
aa0ee1dd7c | ||
|
|
59254f03de | ||
|
|
703a8d1b06 | ||
|
|
f280277a1c | ||
|
|
24c2560002 | ||
|
|
47db712229 | ||
|
|
072ad3d903 | ||
|
|
05dca82411 | ||
|
|
94650a1159 | ||
|
|
4c5cae11db | ||
|
|
42bb87adef | ||
|
|
3d7a729a50 | ||
|
|
019b2d7d89 | ||
|
|
de0dc4af8e | ||
|
|
6765f4d03a | ||
|
|
7bea90e4d1 | ||
|
|
ac4762f9d7 | ||
|
|
1195309b61 | ||
|
|
8ce77e23ba | ||
|
|
5cae2ce400 | ||
|
|
b8c9f9772e | ||
|
|
c3e39b6e15 | ||
|
|
77a391448b | ||
|
|
484d7888cf | ||
|
|
187f1929fe | ||
|
|
0a4333645b | ||
|
|
310506e5af | ||
|
|
0b33ed4299 |
@@ -6,7 +6,9 @@ on:
|
||||
- release/*
|
||||
- develop
|
||||
- main
|
||||
pull_request:
|
||||
# uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
|
||||
# pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
@@ -20,6 +22,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Cache node modules
|
||||
id: cache
|
||||
@@ -45,7 +49,7 @@ jobs:
|
||||
|
||||
lint-pr-title:
|
||||
needs: node-modules
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
name: Verify PR title
|
||||
uses: ./.github/workflows/lint-pr.yml
|
||||
secrets: inherit
|
||||
@@ -60,6 +64,7 @@ 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
|
||||
@@ -107,65 +112,82 @@ jobs:
|
||||
echo "Branch slug: ${branch_slug}"
|
||||
echo ">>>> eof debug"
|
||||
|
||||
projects_e2e=""
|
||||
projects_array=()
|
||||
|
||||
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_e2e+='"governance-e2e" '
|
||||
projects_array+=("governance")
|
||||
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
|
||||
fi
|
||||
if echo "$affected" | grep -q trading; then
|
||||
echo "Trading is affected"
|
||||
projects_e2e+='"trading-e2e" '
|
||||
projects_array+=("trading")
|
||||
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
|
||||
fi
|
||||
if echo "$affected" | grep -q explorer; then
|
||||
echo "Explorer is affected"
|
||||
projects_e2e+='"explorer-e2e" '
|
||||
projects_array+=("explorer")
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
if [[ -z "$projects_e2e" ]]; then
|
||||
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
|
||||
if [[ ${#projects_array[@]} -eq 0 ]]; then
|
||||
projects_array=("governance" "trading" "explorer")
|
||||
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
|
||||
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
|
||||
|
||||
# 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
|
||||
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+=' "multisig-signer" '
|
||||
|
||||
projects_array+=("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+=' "multisig-signer" '
|
||||
|
||||
projects_array+=("multisig-signer")
|
||||
fi
|
||||
if echo "$affected" | grep -q static; then
|
||||
echo "static is affected"
|
||||
echo "Deploying static on s3"
|
||||
projects+=' "static" '
|
||||
|
||||
projects_array+=("static")
|
||||
fi
|
||||
if echo "$affected" | grep -q ui-toolkit; then
|
||||
echo "ui-toolkit is affected"
|
||||
echo "Deploying ui-toolkit on s3"
|
||||
projects+=' "ui-toolkit" '
|
||||
|
||||
projects_array+=("ui-toolkit")
|
||||
fi
|
||||
fi
|
||||
|
||||
projects_e2e=${projects_e2e%?}
|
||||
projects_e2e=[${projects_e2e// /,}]
|
||||
projects=[${projects// /,}]
|
||||
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||
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
|
||||
|
||||
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
|
||||
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
|
||||
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
|
||||
@@ -182,7 +204,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:
|
||||
@@ -192,7 +214,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:
|
||||
@@ -203,7 +225,7 @@ jobs:
|
||||
needs:
|
||||
- publish-dist
|
||||
- lint-test-build
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
timeout-minutes: 60
|
||||
name: '(CD) comment preview links'
|
||||
steps:
|
||||
|
||||
@@ -33,6 +33,7 @@ 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
|
||||
|
||||
@@ -11,6 +11,8 @@ 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
|
||||
|
||||
@@ -19,6 +19,8 @@ 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
|
||||
@@ -31,7 +33,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Container registry (ghcr)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -143,7 +145,7 @@ jobs:
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
|
||||
|
||||
- name: Image digest
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
||||
- name: Sanity check docker image
|
||||
@@ -158,7 +160,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: ghcr-push
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -228,7 +230,7 @@ jobs:
|
||||
|
||||
- name: Add preview label
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
|
||||
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 } from 'ag-grid-community';
|
||||
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
|
||||
type AssetsTableProps = {
|
||||
data: AssetFieldsFragment[] | null;
|
||||
@@ -31,6 +31,58 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: t('Symbol'), field: 'symbol' },
|
||||
{ headerName: t('Name'), field: 'name' },
|
||||
{
|
||||
flex: 2,
|
||||
headerName: t('ID'),
|
||||
field: 'id',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
},
|
||||
{
|
||||
colId: 'type',
|
||||
headerName: t('Type'),
|
||||
field: 'source.__typename',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
valueFormatter: ({ value }: { value?: string }) =>
|
||||
value ? AssetTypeMapping[value].value : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'status',
|
||||
hide: window.innerWidth < BREAKPOINT_MD,
|
||||
valueFormatter: ({ value }: { value?: string }) =>
|
||||
value ? AssetStatusMapping[value].value : '',
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
headerName: '',
|
||||
sortable: false,
|
||||
filter: false,
|
||||
resizable: false,
|
||||
wrapText: true,
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
navigate(value);
|
||||
}}
|
||||
>
|
||||
{t('View details')}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
},
|
||||
],
|
||||
[navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
@@ -46,60 +98,11 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data }: RowClickedEvent) => {
|
||||
navigate(data.id);
|
||||
}}
|
||||
>
|
||||
<AgGridColumn headerName={t('Symbol')} field="symbol" />
|
||||
<AgGridColumn headerName={t('Name')} field="name" />
|
||||
<AgGridColumn
|
||||
flex="2"
|
||||
headerName={t('ID')}
|
||||
field="id"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="type"
|
||||
headerName={t('Type')}
|
||||
field="source.__typename"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
valueFormatter={({ value }: { value?: string }) =>
|
||||
value && AssetTypeMapping[value].value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="status"
|
||||
hide={window.innerWidth < BREAKPOINT_MD}
|
||||
valueFormatter={({ value }: { value?: string }) =>
|
||||
value && AssetStatusMapping[value].value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
headerName=""
|
||||
sortable={false}
|
||||
filter={false}
|
||||
resizable={false}
|
||||
wrapText={true}
|
||||
field="id"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
navigate(value);
|
||||
}}
|
||||
>
|
||||
{t('View details')}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { MarketFieldsFragment } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
@@ -39,54 +40,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
|
||||
navigate(data.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
colId="code"
|
||||
headerName={t('Code')}
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="name"
|
||||
headerName={t('Name')}
|
||||
field="tradableInstrument.instrument.name"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="state"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
valueGetter={({
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
colId: 'code',
|
||||
headerName: t('Code'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
},
|
||||
{
|
||||
colId: 'name',
|
||||
headerName: t('Name'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketFieldsFragment>) => {
|
||||
return data?.state ? MarketStateMapping[data?.state] : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="asset"
|
||||
headerName={t('Settlement asset')}
|
||||
field="tradableInstrument.instrument.product.settlementAsset.symbol"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'asset',
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketFieldsFragment,
|
||||
@@ -105,19 +86,19 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
) : (
|
||||
''
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
flex={2}
|
||||
headerName={t('Market ID')}
|
||||
field="id"
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
headerName=""
|
||||
field="id"
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
flex: 2,
|
||||
headerName: t('Market ID'),
|
||||
field: 'id',
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
headerName: '',
|
||||
field: 'id',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
|
||||
value ? (
|
||||
@@ -126,9 +107,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
</Link>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
),
|
||||
},
|
||||
],
|
||||
[openAssetDetailsDialog]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
|
||||
overlayNoRowsTemplate={t('This chain has no markets')}
|
||||
domLayout="autoHeight"
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
|
||||
navigate(data.id);
|
||||
}
|
||||
/>
|
||||
</AgGrid>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
@@ -9,7 +8,7 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import type { RowClickedEvent, ColDef } from 'ag-grid-community';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -64,7 +63,128 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
title: '',
|
||||
content: null,
|
||||
});
|
||||
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
colId: 'title',
|
||||
headerName: t('Title'),
|
||||
field: 'rationale.title',
|
||||
flex: 2,
|
||||
wrapText: true,
|
||||
},
|
||||
{
|
||||
colId: 'type',
|
||||
maxWidth: 180,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Type'),
|
||||
field: 'terms.change.__typename',
|
||||
},
|
||||
{
|
||||
maxWidth: 100,
|
||||
headerName: t('State'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
maxWidth: 100,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Voting'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="uppercase flex h-full items-center justify-center pt-2">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'cDate',
|
||||
maxWidth: 150,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Closing date'),
|
||||
field: 'terms.closingDatetime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.closingDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'eDate',
|
||||
maxWidth: 150,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Enactment date'),
|
||||
field: 'terms.enactmentDatetime',
|
||||
valueFormatte: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.enactmentDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
minWidth: window.innerWidth > BREAKPOINT_MD ? 221 : 80,
|
||||
maxWidth: 221,
|
||||
sortable: false,
|
||||
filter: false,
|
||||
resizable: false,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
const proposalPage = tokenLink(
|
||||
TOKEN_PROPOSAL.replace(':id', data?.id || '')
|
||||
);
|
||||
const openDialog = () => {
|
||||
if (!data) return;
|
||||
setDialog({
|
||||
open: true,
|
||||
title: data.rationale.title,
|
||||
content: data.terms,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="pb-1">
|
||||
<button className="underline max-md:hidden" onClick={openDialog}>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ExternalLink className="max-md:hidden" href={proposalPage}>
|
||||
{t('Open in Governance')}
|
||||
</ExternalLink>
|
||||
<ExternalLink className="md:hidden" href={proposalPage}>
|
||||
{t('Open')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[requiredMajorityPercentage, tokenLink]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
@@ -83,6 +203,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
filterParams: { buttons: ['reset'] },
|
||||
autoHeight: true,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus={true}
|
||||
onRowClicked={({ data, event }: RowClickedEvent) => {
|
||||
if (
|
||||
@@ -94,128 +215,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
window.open(proposalPage, '_blank');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
colId="title"
|
||||
headerName={t('Title')}
|
||||
field="rationale.title"
|
||||
flex={2}
|
||||
wrapText={true}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="type"
|
||||
maxWidth={180}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Type')}
|
||||
field="terms.change.__typename"
|
||||
/>
|
||||
<AgGridColumn
|
||||
maxWidth={100}
|
||||
headerName={t('State')}
|
||||
field="state"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="voting"
|
||||
maxWidth={100}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Voting')}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="uppercase flex h-full items-center justify-center pt-2">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="cDate"
|
||||
maxWidth={150}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Closing date')}
|
||||
field="terms.closingDatetime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.closingDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="eDate"
|
||||
maxWidth={150}
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
headerName={t('Enactment date')}
|
||||
field="terms.enactmentDatetime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.enactmentDatetime'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="actions"
|
||||
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80}
|
||||
maxWidth={221}
|
||||
sortable={false}
|
||||
filter={false}
|
||||
resizable={false}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
const proposalPage = tokenLink(
|
||||
TOKEN_PROPOSAL.replace(':id', data?.id || '')
|
||||
);
|
||||
const openDialog = () => {
|
||||
if (!data) return;
|
||||
setDialog({
|
||||
open: true,
|
||||
title: data.rationale.title,
|
||||
content: data.terms,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="pb-1">
|
||||
<button
|
||||
className="underline max-md:hidden"
|
||||
onClick={openDialog}
|
||||
>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ExternalLink className="max-md:hidden" href={proposalPage}>
|
||||
{t('Open in Governance')}
|
||||
</ExternalLink>
|
||||
<ExternalLink className="md:hidden" href={proposalPage}>
|
||||
{t('Open')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
<JsonViewerDialog
|
||||
open={dialog.open}
|
||||
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@tailwind base;
|
||||
|
||||
@@ -49,7 +49,7 @@ module.exports = defineConfig({
|
||||
vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7',
|
||||
vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de',
|
||||
txTimeout: { timeout: 70000 },
|
||||
epochTimeout: { timeout: 10000 },
|
||||
epochTimeout: { timeout: 12000 },
|
||||
blockConfirmations: 3,
|
||||
grepTags: '@regression @smoke @slow',
|
||||
grepFilterSpecs: true,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
export const previousEpochData = {
|
||||
epoch: {
|
||||
id: '7611',
|
||||
validatorsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'cd96782bc0ad5679869cf69fe7838a92212da7f53b4a214bed68067117494122',
|
||||
stakedTotal: '3154229668720612941799',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '0',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211713765544955625',
|
||||
stakeScore: '0.2016321576618625',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '887d936f797a47032eceb572a13b69b581d3b1fa595a7d021a3e3cf2a5d2acfd',
|
||||
stakedTotal: '3151161904761904764551',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '1',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211507855409133255',
|
||||
stakeScore: '0.2014360527706031',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'NodesConnection',
|
||||
},
|
||||
__typename: 'Epoch',
|
||||
},
|
||||
};
|
||||
@@ -58,8 +58,6 @@ context(
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
ethereumWalletConnect();
|
||||
// this is a workaround for #2422 which can be removed once issue is resolved
|
||||
cy.associateTokensToVegaWallet('4');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
clickOnValidatorFromList,
|
||||
waitForBeginningOfEpoch,
|
||||
} from '../../support/staking.functions';
|
||||
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
|
||||
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
@@ -30,6 +32,7 @@ const normalisedVotingPowerToolTip =
|
||||
'[data-testid="normalised-voting-power-tooltip"]';
|
||||
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
|
||||
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
|
||||
const multisigPenaltyToolTip = '[data-testid="multisig-error-tooltip"]';
|
||||
const epochCountDown = '[data-testid="epoch-countdown"]';
|
||||
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
|
||||
|
||||
@@ -151,6 +154,22 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.wrap($pendingStake).should('contain.text', '0.00');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to see multisig error', function () {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'PreviousEpoch', previousEpochData);
|
||||
});
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('total-penalty').first().realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
|
||||
cy.getByTestId('total-penalty').eq(1).realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -282,33 +282,33 @@ context(
|
||||
// 2002-SINC-016
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: 10.0,
|
||||
},
|
||||
{
|
||||
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
|
||||
name: 'DAI (fake)',
|
||||
symbol: 'fDAI',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
expectedAmount: 2.0,
|
||||
},
|
||||
{
|
||||
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
|
||||
name: 'BTC (fake)',
|
||||
symbol: 'fBTC',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
expectedAmount: 6.0,
|
||||
},
|
||||
{
|
||||
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
|
||||
name: 'EURO (fake)',
|
||||
symbol: 'fEURO',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
expectedAmount: 8.0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -319,12 +319,6 @@ context(
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.at.least',
|
||||
5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
for (const { name, symbol, expectedAmount } of assets) {
|
||||
@@ -338,10 +332,10 @@ context(
|
||||
.contains(name)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('have.length.at.least', 4)
|
||||
.then(parseFloat)
|
||||
.should('be.gte', parseFloat(expectedAmount));
|
||||
.then((elementAmount) => {
|
||||
const displayedAmount = parseFloat(elementAmount.text());
|
||||
expect(displayedAmount).be.gte(expectedAmount);
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(name)
|
||||
|
||||
@@ -37,7 +37,7 @@ export function navigateTo(page: navigation) {
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
cy.get(page).eq(0).click({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
+238
-254
@@ -21,11 +21,14 @@ import {
|
||||
HealthBar,
|
||||
TooltipCellComponent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { GetRowIdParams, RowClickedEvent } from 'ag-grid-community';
|
||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type {
|
||||
GetRowIdParams,
|
||||
RowClickedEvent,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
|
||||
import { Grid } from '../../grid';
|
||||
import { HealthDialog } from '../../health-dialog';
|
||||
@@ -39,6 +42,234 @@ export const MarketList = () => {
|
||||
const consoleLink = useLinks(DApp.Console);
|
||||
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market (futures)'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
cellRenderer: ({ value, data }: { value: string; data: Market }) => {
|
||||
return (
|
||||
<>
|
||||
<span className="leading-3">{value}</span>
|
||||
<span className="leading-3">
|
||||
{
|
||||
data?.tradableInstrument?.instrument?.product?.settlementAsset
|
||||
?.symbol
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
minWidth: 100,
|
||||
flex: 1,
|
||||
headerTooltip: t('The market name and settlement asset'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Market Code'),
|
||||
headerTooltip: t(
|
||||
'The market code is a unique identifier for this market'
|
||||
),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Type'),
|
||||
headerTooltip: t('Type'),
|
||||
field: 'tradableInstrument.instrument.product.__typename',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Last Price'),
|
||||
headerTooltip: t('Latest price for this market'),
|
||||
field: 'data.markPrice',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
|
||||
value && data
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Change (24h)'),
|
||||
headerTooltip: t('Change in price over the last 24h'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
|
||||
if (data && data.candles) {
|
||||
const prices = data.candles.map((candle) => candle.close);
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={prices}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
);
|
||||
} else return <div>{t('-')}</div>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Volume (24h)'),
|
||||
field: 'dayVolume',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
|
||||
value && data
|
||||
? `${addDecimalsFormatNumber(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
)} (${displayChange(data.volumeChange)})`
|
||||
: '-',
|
||||
headerTooltip: t('The trade volume over the last 24h'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Total staked by LPs'),
|
||||
field: 'liquidityCommitted',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value.toString(),
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Target stake'),
|
||||
field: 'target',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'target'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-',
|
||||
headerTooltip: t(
|
||||
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('% Target stake met'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
if (data) {
|
||||
const roundedPercentage =
|
||||
parseInt(
|
||||
(data.liquidityCommitted / parseFloat(data.target)).toFixed(0)
|
||||
) * 100;
|
||||
const display = Number.isNaN(roundedPercentage)
|
||||
? 'N/A'
|
||||
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
|
||||
return display;
|
||||
} else return '-';
|
||||
},
|
||||
headerTooltip: t('% Target stake met'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Fee levels'),
|
||||
field: 'fees',
|
||||
valueFormatter: ({ value }: VegaValueFormatterParams<Market, 'fees'>) =>
|
||||
value ? `${value.factors.liquidityFee}%` : '-',
|
||||
headerTooltip: t('Fee level for this market'),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => {
|
||||
return <Status trigger={data.data?.trigger} tradingMode={value} />;
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The current market status - those below the target stake mark are most in need of liquidity'
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerComponent: () => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => (
|
||||
<HealthBar
|
||||
target={data.target}
|
||||
decimals={
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
levels={data.feeLevels}
|
||||
intent={intentForStatus(value)}
|
||||
/>
|
||||
),
|
||||
sortable: false,
|
||||
cellStyle: { overflow: 'unset' },
|
||||
},
|
||||
{
|
||||
headerName: t('Age'),
|
||||
field: 'marketTimestamps.open',
|
||||
headerTooltip: t('Age of the market'),
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
|
||||
return value ? formatDistanceToNow(new Date(value)) : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Closing Time'),
|
||||
field: 'tradableInstrument.instrument.metadata.tags',
|
||||
headerTooltip: t('Closing time of the market'),
|
||||
valueFormatter: ({ data }: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
@@ -64,258 +295,11 @@ export const MarketList = () => {
|
||||
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
isRowClickable
|
||||
tooltipShowDelay={500}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market (futures)')}
|
||||
field="tradableInstrument.instrument.name"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: string;
|
||||
data: Market;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<span className="leading-3">{value}</span>
|
||||
<span className="leading-3">
|
||||
{
|
||||
data?.tradableInstrument?.instrument?.product
|
||||
?.settlementAsset?.symbol
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
flex="1"
|
||||
headerTooltip={t('The market name and settlement asset')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Market Code')}
|
||||
headerTooltip={t(
|
||||
'The market code is a unique identifier for this market'
|
||||
)}
|
||||
field="tradableInstrument.instrument.code"
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Type')}
|
||||
headerTooltip={t('Type')}
|
||||
field="tradableInstrument.instrument.product.__typename"
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Last Price')}
|
||||
headerTooltip={t('Latest price for this market')}
|
||||
field="data.markPrice"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.markPrice'>) =>
|
||||
value && data
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Change (24h)')}
|
||||
headerTooltip={t('Change in price over the last 24h')}
|
||||
cellRenderer={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'data.candles'>) => {
|
||||
if (data && data.candles) {
|
||||
const prices = data.candles.map((candle) => candle.close);
|
||||
return (
|
||||
<PriceChangeCell
|
||||
candles={prices}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
);
|
||||
} else return <div>{t('-')}</div>;
|
||||
}}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Volume (24h)')}
|
||||
field="dayVolume"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'dayVolume'>) =>
|
||||
value && data
|
||||
? `${addDecimalsFormatNumber(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
)} (${displayChange(data.volumeChange)})`
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t('The trade volume over the last 24h')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Total staked by LPs')}
|
||||
field="liquidityCommitted"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'liquidityCommitted'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value.toString(),
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t(
|
||||
'The amount of funds allocated to provide liquidity'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Target stake')}
|
||||
field="target"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, 'target'>) =>
|
||||
data && value
|
||||
? formatWithAsset(
|
||||
value,
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
)
|
||||
: '-'
|
||||
}
|
||||
headerTooltip={t(
|
||||
'The ideal committed liquidity to operate the market. If total commitment currently below this level then LPs can set the fee level with new commitment.'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('% Target stake met')}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, ''>) => {
|
||||
if (data) {
|
||||
const roundedPercentage =
|
||||
parseInt(
|
||||
(data.liquidityCommitted / parseFloat(data.target)).toFixed(
|
||||
0
|
||||
)
|
||||
) * 100;
|
||||
const display = Number.isNaN(roundedPercentage)
|
||||
? 'N/A'
|
||||
: formatNumberPercentage(toBigNum(roundedPercentage, 0), 0);
|
||||
return display;
|
||||
} else return '-';
|
||||
}}
|
||||
headerTooltip={t('% Target stake met')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Fee levels')}
|
||||
field="fees"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'fees'>) =>
|
||||
value ? `${value.factors.liquidityFee}%` : '-'
|
||||
}
|
||||
headerTooltip={t('Fee level for this market')}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="tradingMode"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => {
|
||||
return (
|
||||
<Status trigger={data.data?.trigger} tradingMode={value} />
|
||||
);
|
||||
}}
|
||||
headerTooltip={t(
|
||||
'The current market status - those below the target stake mark are most in need of liquidity'
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerComponent={() => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('Health')}</span>{' '}
|
||||
<button
|
||||
onClick={() => setIsHealthDialogOpen(true)}
|
||||
aria-label={t('open tooltip')}
|
||||
>
|
||||
<Icon name="info-sign" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
field="tradingMode"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: Schema.MarketTradingMode;
|
||||
data: Market;
|
||||
}) => (
|
||||
<HealthBar
|
||||
target={data.target}
|
||||
decimals={
|
||||
data.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
levels={data.feeLevels}
|
||||
intent={intentForStatus(value)}
|
||||
/>
|
||||
)}
|
||||
sortable={false}
|
||||
cellStyle={{ overflow: 'unset' }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Age')}
|
||||
field="marketTimestamps.open"
|
||||
headerTooltip={t('Age of the market')}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Market, 'marketTimestamps.open'>) => {
|
||||
return value ? formatDistanceToNow(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Closing Time')}
|
||||
field="tradableInstrument.instrument.metadata.tags"
|
||||
headerTooltip={t('Closing time of the market')}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Market, ''>) => {
|
||||
let expiry;
|
||||
if (data?.tradableInstrument.instrument.metadata.tags) {
|
||||
expiry = getExpiryDate(
|
||||
data?.tradableInstrument.instrument.metadata.tags,
|
||||
data?.marketTimestamps.close,
|
||||
data?.state
|
||||
);
|
||||
}
|
||||
return expiry ? expiry : '-';
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
/>
|
||||
<HealthDialog
|
||||
isOpen={isHealthDialogOpen}
|
||||
onChange={() => {
|
||||
|
||||
+73
-70
@@ -1,7 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { GetRowIdParams } from 'ag-grid-community';
|
||||
import type { GetRowIdParams, ColDef } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
import type {
|
||||
@@ -36,6 +35,75 @@ export const LPProvidersGrid = ({
|
||||
};
|
||||
}) => {
|
||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []);
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('LPs'),
|
||||
field: 'party.id',
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
headerTooltip: t('Liquidity providers'),
|
||||
},
|
||||
{
|
||||
headerName: t('Duration'),
|
||||
valueFormatter: formatToHours,
|
||||
field: 'createdAt',
|
||||
headerTooltip: t('Time in market'),
|
||||
},
|
||||
{
|
||||
headerName: t('Equity-like share'),
|
||||
field: 'equityLikeShare',
|
||||
valueFormatter: ({ value }: { value?: string | null }) => {
|
||||
return value
|
||||
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
|
||||
: '';
|
||||
},
|
||||
headerTooltip: t(
|
||||
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
|
||||
),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('committed bond'),
|
||||
field: 'commitmentAmount',
|
||||
valueFormatter: ({ value }: { value?: string | null }) =>
|
||||
value ? formatWithAsset(value, settlementAsset) : '0',
|
||||
headerTooltip: t('The amount of funds allocated to provide liquidity'),
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
headerName: t('Margin Req.'),
|
||||
field: 'margin',
|
||||
headerTooltip: t(
|
||||
'Margin required for arising positions based on liquidity commitment'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('24h Fees'),
|
||||
field: 'fees',
|
||||
headerTooltip: t(
|
||||
'Total fees earned by the liquidity provider in the last 24 hours'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Fee level'),
|
||||
valueFormatter: ({ value }: { value?: string | null }) => `${value}%`,
|
||||
field: 'fee',
|
||||
headerTooltip: t(
|
||||
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('APY'),
|
||||
field: 'apy',
|
||||
headerTooltip: t(
|
||||
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
|
||||
),
|
||||
},
|
||||
],
|
||||
[settlementAsset]
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
@@ -49,74 +117,9 @@ export const LPProvidersGrid = ({
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
minWidth: 100,
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={getRowId}
|
||||
rowHeight={92}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('LPs')}
|
||||
field="party.id"
|
||||
flex="1"
|
||||
minWidth={100}
|
||||
headerTooltip={t('Liquidity providers')}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Duration')}
|
||||
valueFormatter={formatToHours}
|
||||
field="createdAt"
|
||||
headerTooltip={t('Time in market')}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Equity-like share')}
|
||||
field="equityLikeShare"
|
||||
valueFormatter={({ value }: { value?: string | null }) => {
|
||||
return value
|
||||
? `${parseFloat(parseFloat(value).toFixed(2)) * 100}%`
|
||||
: '';
|
||||
}}
|
||||
headerTooltip={t(
|
||||
'The share of the markets liquidity held - the earlier you commit liquidity the greater % fees you earn'
|
||||
)}
|
||||
minWidth={140}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('committed bond')}
|
||||
field="commitmentAmount"
|
||||
valueFormatter={({ value }: { value?: string | null }) =>
|
||||
value ? formatWithAsset(value, settlementAsset) : '0'
|
||||
}
|
||||
headerTooltip={t('The amount of funds allocated to provide liquidity')}
|
||||
minWidth={140}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Margin Req.')}
|
||||
field="margin"
|
||||
headerTooltip={t(
|
||||
'Margin required for arising positions based on liquidity commitment'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('24h Fees')}
|
||||
field="fees"
|
||||
headerTooltip={t(
|
||||
'Total fees earned by the liquidity provider in the last 24 hours'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Fee level')}
|
||||
valueFormatter={({ value }: { value?: string | null }) => `${value}%`}
|
||||
field="fee"
|
||||
headerTooltip={t(
|
||||
"The market's liquidity fee, or the percentage of a trade's value which is collected from the price taker for every trade"
|
||||
)}
|
||||
/>
|
||||
|
||||
<AgGridColumn
|
||||
headerName={t('APY')}
|
||||
field="apy"
|
||||
headerTooltip={t(
|
||||
'An annualised estimate based on the total liquidity provision fees and maker fees collected by liquidity providers, the maximum margin needed and maximum commitment (bond) over the course of 7 epochs'
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import type {
|
||||
AgGridReactProps,
|
||||
@@ -7,18 +6,17 @@ import type {
|
||||
AgGridReact as AgGridReactType,
|
||||
} from 'ag-grid-react';
|
||||
import classNames from 'classnames';
|
||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||
import 'ag-grid-community/styles/ag-grid.css';
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
|
||||
import './grid.scss';
|
||||
|
||||
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
isRowClickable?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||
export const Grid = ({ isRowClickable, ...props }: Props) => {
|
||||
const gridRef = useRef<AgGridReactType | null>(null);
|
||||
|
||||
const resizeGrid = useCallback(() => {
|
||||
@@ -44,8 +42,6 @@ export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||
onGridReady={handleOnGridReady}
|
||||
suppressRowClickSelection
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</AgGridReact>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ 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(1)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderStatus}']`).should(
|
||||
'have.text',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { checkSorting, aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||
import { positionsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
@@ -15,13 +14,12 @@ 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
|
||||
@@ -64,7 +62,14 @@ 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 = [
|
||||
{
|
||||
@@ -103,7 +108,7 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
@@ -164,8 +169,9 @@ 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(() => {
|
||||
@@ -180,29 +186,33 @@ 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 };
|
||||
});
|
||||
});
|
||||
|
||||
cy.reload();
|
||||
cy.window().then((win) => {
|
||||
Object.keys(localStorageCopy).forEach((key) => {
|
||||
win.localStorage.setItem(key, localStorageCopy[key]);
|
||||
});
|
||||
});
|
||||
// This test depends on the previous one
|
||||
it('Has persisted column widths', () => {
|
||||
const width = 400;
|
||||
|
||||
// 7004-POSI-012
|
||||
cy.get('[col-id="marketName"]')
|
||||
.invoke('width')
|
||||
.should('equal', elementWidth);
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
it('Scroll horizontally', () => {
|
||||
@@ -291,6 +301,7 @@ 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')
|
||||
@@ -326,6 +337,7 @@ function validatePositionsDisplayed(multiKey = false) {
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
|
||||
function assertPNLColor(
|
||||
pnlSelector: string,
|
||||
positiveClass: string,
|
||||
@@ -347,6 +359,7 @@ function assertPNLColor(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function visitAndClickPositions() {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
|
||||
@@ -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_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_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,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_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_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
|
||||
|
||||
@@ -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_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_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
|
||||
|
||||
@@ -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_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_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
|
||||
|
||||
@@ -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_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_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,7 +1,5 @@
|
||||
import {
|
||||
matchFilter,
|
||||
liquidityProvisionsDataProvider,
|
||||
LiquidityTable,
|
||||
lpAggregatedDataProvider,
|
||||
useCheckLiquidityStatus,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
@@ -24,18 +22,16 @@ import {
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { memo, useEffect, 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',
|
||||
@@ -49,65 +45,6 @@ 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,10 +19,7 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { HeaderTitle } from '../../components/header';
|
||||
import {
|
||||
@@ -49,7 +46,6 @@ const MarketBottomPanel = memo(
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
return 'xxxl' === screenSize ? (
|
||||
<ResizableGrid
|
||||
@@ -69,10 +65,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -81,10 +73,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -93,22 +81,12 @@ 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}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketAllOrders"
|
||||
/>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
@@ -116,7 +94,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -134,8 +111,6 @@ const MarketBottomPanel = memo(
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
storeKey="marketPositions"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -145,7 +120,6 @@ const MarketBottomPanel = memo(
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
storeKey="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -158,10 +132,7 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketPositions"
|
||||
/>
|
||||
<TradingViews.positions.component onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
@@ -169,10 +140,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -181,10 +148,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -193,22 +156,12 @@ 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}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
storeKey="marketAllOrders"
|
||||
/>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
@@ -216,7 +169,6 @@ const MarketBottomPanel = memo(
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -226,7 +178,6 @@ const MarketBottomPanel = memo(
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
storeKey="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
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 { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { LiquidityContainer } from '../liquidity/liquidity';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
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';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -65,25 +66,25 @@ export const TradingViews = {
|
||||
positions: { label: 'Positions', component: PositionsContainer },
|
||||
activeOrders: {
|
||||
label: 'Active',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Open} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Open} />
|
||||
),
|
||||
},
|
||||
closedOrders: {
|
||||
label: 'Closed',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Closed} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Closed} />
|
||||
),
|
||||
},
|
||||
rejectedOrders: {
|
||||
label: 'Rejected',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Rejected} />
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Rejected} />
|
||||
),
|
||||
},
|
||||
orders: {
|
||||
label: 'All',
|
||||
component: OrderListContainer,
|
||||
component: OrdersContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
|
||||
@@ -313,7 +313,6 @@ const ClosedMarketsDataGrid = ({
|
||||
minWidth: 100,
|
||||
}}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
storeKey="closedMarkets"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
@@ -17,17 +16,13 @@ export const DepositsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="h-full relative">
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
</div>
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
{!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,29 +1,39 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
|
||||
import { usePaneLayout } from '@vegaprotocol/react-helpers';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { DepositsContainer } from './deposits-container';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { LedgerContainer } from '@vegaprotocol/ledger';
|
||||
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 { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
} from '../../components/resizable-grid';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
if (!ready || ready.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="bg-vega-blue-450 text-white text-[10px] rounded p-[3px] pb-[2px] leading-none">
|
||||
{ready.length}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
@@ -34,7 +44,6 @@ 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 (
|
||||
@@ -50,29 +59,17 @@ export const Portfolio = () => {
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<PositionsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
storeKey="portfolioPositions"
|
||||
allKeys
|
||||
/>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<VegaWalletContainer>
|
||||
<OrderListContainer
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
storeKey="portfolioOrders"
|
||||
/>
|
||||
<OrdersContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<FillsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey="portfolioFills"
|
||||
/>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
@@ -92,10 +89,7 @@ export const Portfolio = () => {
|
||||
<Tabs storageKey="console-portfolio-bottom">
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountsContainer
|
||||
storeKey="portfolioCollateral"
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
<AccountsContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')}>
|
||||
@@ -103,7 +97,11 @@ export const Portfolio = () => {
|
||||
<DepositsContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="withdrawals" name={t('Withdrawals')}>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
name={t('Withdrawals')}
|
||||
indicator={<WithdrawalsIndicator />}
|
||||
>
|
||||
<WithdrawalsContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
WithdrawalsTable,
|
||||
useIncompleteWithdrawals,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -17,6 +18,7 @@ export const WithdrawalsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
|
||||
return (
|
||||
<VegaWalletContainer>
|
||||
@@ -25,6 +27,8 @@ export const WithdrawalsContainer = () => {
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
ready={ready}
|
||||
delayed={delayed}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
|
||||
@@ -8,16 +8,19 @@ 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();
|
||||
@@ -26,6 +29,12 @@ 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);
|
||||
@@ -51,7 +60,7 @@ export const AccountsContainer = ({
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
storeKey={storeKey}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
{!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">
|
||||
@@ -75,3 +84,9 @@ export const AccountsContainer = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useAccountStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_accounts_store',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { DefaultWeb3ProviderContextShape } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
createConnectors,
|
||||
Web3Provider as Web3ProviderInternal,
|
||||
useWeb3ConnectStore,
|
||||
createDefaultProvider,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
@@ -17,10 +20,13 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const connectors = useWeb3ConnectStore((store) => store.connectors);
|
||||
const initializeConnectors = useWeb3ConnectStore((store) => store.initialize);
|
||||
const [defaultProvider, setDefaultProvider] = useState<
|
||||
DefaultWeb3ProviderContextShape['provider'] | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (config?.chain_id) {
|
||||
return initializeConnectors(
|
||||
initializeConnectors(
|
||||
createConnectors(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id),
|
||||
@@ -29,6 +35,11 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
),
|
||||
Number(config.chain_id)
|
||||
);
|
||||
const defaultProvider = createDefaultProvider(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id)
|
||||
);
|
||||
setDefaultProvider(defaultProvider);
|
||||
}
|
||||
}, [
|
||||
config?.chain_id,
|
||||
@@ -49,7 +60,10 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
}}
|
||||
noDataMessage={t('Could not fetch Ethereum configuration')}
|
||||
>
|
||||
<Web3ProviderInternal connectors={connectors}>
|
||||
<Web3ProviderInternal
|
||||
connectors={connectors}
|
||||
defaultProvider={defaultProvider}
|
||||
>
|
||||
<>{children}</>
|
||||
</Web3ProviderInternal>
|
||||
</AsyncRenderer>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './fills-container';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ledger-container';
|
||||
@@ -0,0 +1,36 @@
|
||||
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',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './liquidity-container';
|
||||
@@ -0,0 +1,93 @@
|
||||
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',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './orders-container';
|
||||
@@ -0,0 +1,106 @@
|
||||
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
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
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 };
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './positions-container';
|
||||
+19
-7
@@ -1,21 +1,28 @@
|
||||
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 { PositionsManager } from './positions-manager';
|
||||
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 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>
|
||||
@@ -38,8 +45,13 @@ export const PositionsContainer = ({
|
||||
partyIds={partyIds}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
noBottomPlaceholder={noBottomPlaceholder}
|
||||
storeKey={storeKey}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const usePositionsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_positions_store',
|
||||
})
|
||||
);
|
||||
@@ -1,17 +1,25 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
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', () => {
|
||||
it('click on checkbox should be properly handled', async () => {
|
||||
const helpText = 'My help text';
|
||||
render(<TelemetryApproval helpText={helpText} />);
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
act(() => {
|
||||
screen.getByRole('checkbox').click();
|
||||
});
|
||||
await userEvent.click(screen.getByRole('checkbox'));
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
|
||||
@@ -11,6 +11,7 @@ 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();
|
||||
@@ -31,9 +32,7 @@ export const WelcomeDialog = () => {
|
||||
);
|
||||
|
||||
const isRiskDialogNeeded =
|
||||
riskAccepted !== 'true' &&
|
||||
VEGA_ENV !== Networks.MAINNET &&
|
||||
!('Cypress' in window);
|
||||
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
|
||||
|
||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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 +0,0 @@
|
||||
export * from './env';
|
||||
@@ -12,22 +12,26 @@ 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 res = renderHook(() => useTelemetryApproval());
|
||||
expect(res.result.current[0]).toEqual(false);
|
||||
expect(res.result.current[1]).toEqual(expect.any(Function));
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual(false);
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('hook should init stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](true);
|
||||
result.current[1](true);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryInit).toHaveBeenCalled();
|
||||
@@ -36,9 +40,9 @@ describe('useTelemetryApproval', () => {
|
||||
});
|
||||
|
||||
it('hook should close stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](false);
|
||||
result.current[1](false);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryClose).toHaveBeenCalled();
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { ENV } from '../config';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
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) {
|
||||
SentryInit(ENV.dsn, ENV.envName);
|
||||
if (value && SENTRY_DSN) {
|
||||
SentryInit(SENTRY_DSN, VEGA_ENV);
|
||||
return setValue('1');
|
||||
}
|
||||
SentryClose();
|
||||
removeValue();
|
||||
},
|
||||
[setValue, removeValue]
|
||||
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
|
||||
);
|
||||
return [Boolean(value), setApprove];
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ 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';
|
||||
@@ -155,10 +154,11 @@ const PartyData = () => {
|
||||
};
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
useVegaEagerConnect(Connectors);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: ENV.dsn, env: ENV.envName } : {}
|
||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||
);
|
||||
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
|
||||
@@ -20,10 +20,6 @@ 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 />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham.css';
|
||||
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-balham.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@@ -3,12 +3,17 @@ import { useUpdateNetworkParametersToasts } from '@vegaprotocol/proposals';
|
||||
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
|
||||
import { Routes } from './client-router';
|
||||
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
|
||||
|
||||
export const ToastsManager = () => {
|
||||
useUpdateNetworkParametersToasts();
|
||||
useVegaTransactionToasts();
|
||||
useEthereumTransactionToasts();
|
||||
useEthereumWithdrawApprovalsToasts();
|
||||
useReadyToWithdrawalToasts({
|
||||
withdrawalsLink: `${Routes.PORTFOLIO}`,
|
||||
});
|
||||
|
||||
const toasts = useToasts((store) => store.toasts);
|
||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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,6 +7,7 @@ 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', () => ({
|
||||
@@ -14,6 +15,13 @@ 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(() => {
|
||||
@@ -43,6 +51,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
@@ -55,6 +64,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyTwo"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -72,6 +82,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
rerenderer = rerender;
|
||||
@@ -85,6 +96,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -110,6 +122,7 @@ describe('AccountManager', () => {
|
||||
partyId="partyOne"
|
||||
onClickAsset={jest.fn}
|
||||
isReadOnly={false}
|
||||
gridProps={gridProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ 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,
|
||||
@@ -102,7 +103,7 @@ interface AccountManagerProps {
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
storeKey?: string;
|
||||
gridProps?: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const AccountManager = ({
|
||||
@@ -112,12 +113,11 @@ 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}
|
||||
|
||||
@@ -44,62 +44,79 @@ describe('AccountsTable', () => {
|
||||
});
|
||||
|
||||
it('should apply correct formatting', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const cells = await screen.findAllByRole('gridcell');
|
||||
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
const rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should apply correct formatting in view as user mode', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const cells = await screen.findAllByRole('gridcell');
|
||||
const expectedValues = ['tBTC', '1,256.00', '1,256.00', '2,512.00', ''];
|
||||
expect(cells.length).toBe(expectedValues.length);
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
const rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should not add first asset as pinned', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const rows = await screen.findAllByRole('row');
|
||||
expect(rows.length).toBe(6);
|
||||
it('should add asset as pinned', async () => {
|
||||
const { container, rerender } = render(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
await screen.findAllByRole('rowgroup');
|
||||
let rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount).toBe(0);
|
||||
let pinnedRows = container.querySelector('.ag-floating-top-container');
|
||||
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
|
||||
|
||||
rerender(
|
||||
<AccountTable
|
||||
rowData={singleRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
rows = container.querySelector('.ag-center-cols-container');
|
||||
expect(rows?.childElementCount ?? 0).toBe(1);
|
||||
pinnedRows = container.querySelector('.ag-floating-top-container');
|
||||
expect(pinnedRows?.childElementCount ?? 0).toBe(1);
|
||||
});
|
||||
|
||||
it('should get correct account data', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
IGetRowsParams,
|
||||
RowNode,
|
||||
IRowNode,
|
||||
RowHeightParams,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
@@ -45,8 +45,8 @@ export const percentageValue = (part: string, total: string) => {
|
||||
export const accountValuesComparator = (
|
||||
valueA: string,
|
||||
valueB: string,
|
||||
nodeA: RowNode,
|
||||
nodeB: RowNode
|
||||
nodeA: IRowNode,
|
||||
nodeB: IRowNode
|
||||
) => {
|
||||
if (isNumeric(valueA) && isNumeric(valueB)) {
|
||||
const a = toBigNum(valueA, nodeA.data.asset?.decimals);
|
||||
@@ -73,7 +73,6 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
onClickBreakdown?: (assetId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
@@ -84,20 +83,22 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
onClickDeposit,
|
||||
onClickBreakdown,
|
||||
rowData,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const pinnedAsset = useMemo(() => {
|
||||
if (!props.pinnedAsset) {
|
||||
const pinnedRow = useMemo(() => {
|
||||
if (!pinnedAsset) {
|
||||
return;
|
||||
}
|
||||
const currentPinnedAssetRow = rowData?.find(
|
||||
(row) => row.asset.id === props.pinnedAsset?.id
|
||||
(row) => row.asset.id === pinnedAsset?.id
|
||||
);
|
||||
if (!currentPinnedAssetRow) {
|
||||
return {
|
||||
asset: props.pinnedAsset,
|
||||
asset: pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
@@ -105,7 +106,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
};
|
||||
}
|
||||
return currentPinnedAssetRow;
|
||||
}, [props.pinnedAsset, rowData]);
|
||||
}, [pinnedAsset, rowData]);
|
||||
|
||||
const { getRowHeight } = props;
|
||||
|
||||
@@ -113,17 +114,17 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === props.pinnedAsset?.id &&
|
||||
params.data.asset.id === pinnedAsset?.id &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
[props.pinnedAsset?.id, getRowHeight]
|
||||
[pinnedAsset?.id, getRowHeight]
|
||||
);
|
||||
|
||||
const showDepositButton = pinnedAsset?.balance === '0';
|
||||
const showDepositButton = pinnedRow?.balance === '0';
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
@@ -267,7 +268,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}
|
||||
return props.isReadOnly ? null : (
|
||||
return isReadOnly ? null : (
|
||||
<AccountsActionsDropdown
|
||||
assetId={assetId}
|
||||
assetContractAddress={
|
||||
@@ -295,19 +296,16 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
onClickBreakdown,
|
||||
onClickDeposit,
|
||||
onClickWithdraw,
|
||||
props.isReadOnly,
|
||||
isReadOnly,
|
||||
showDepositButton,
|
||||
]);
|
||||
|
||||
const data = rowData?.filter(
|
||||
(data) => data.asset.id !== props.pinnedAsset?.id
|
||||
);
|
||||
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
@@ -320,7 +318,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
}}
|
||||
columnDefs={colDefs}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
|
||||
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{
|
||||
"presets": ["@nrwl/next/babel"],
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/next/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/accounts",
|
||||
"outputPath": "dist/libs/announcements",
|
||||
"tsConfig": "libs/announcements/tsconfig.lib.json",
|
||||
"project": "libs/announcements/package.json",
|
||||
"entryFile": "libs/announcements/src/index.ts",
|
||||
|
||||
@@ -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-4">
|
||||
<div className="px-4 py-2 flex flex-row flex-wrap gap-2">
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './lib/ag-grid/ag-grid-lazy';
|
||||
export * from './lib/ag-grid/use-column-sizes';
|
||||
|
||||
export * from './lib/column-definitions';
|
||||
|
||||
@@ -9,7 +8,6 @@ export * from './lib/cells/numeric-cell';
|
||||
export * from './lib/cells/price-cell';
|
||||
export * from './lib/cells/price-change-cell';
|
||||
export * from './lib/cells/price-flash-cell';
|
||||
export * from './lib/cells/vol-cell';
|
||||
export * from './lib/cells/centered-grid-cell';
|
||||
export * from './lib/cells/market-name-cell';
|
||||
export * from './lib/cells/order-type-cell';
|
||||
@@ -24,4 +22,4 @@ export * from './lib/type-helpers';
|
||||
export * from './lib/cells/grid-progress-bar';
|
||||
|
||||
export * from './lib/ag-grid-update';
|
||||
export * from './lib/use-bottom-placeholder';
|
||||
export * from './lib/use-datagrid-events';
|
||||
|
||||
@@ -2,23 +2,16 @@ 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,
|
||||
@@ -36,12 +29,7 @@ export const AgGridThemed = ({
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses} style={style}>
|
||||
<AgGridReact
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
{...commonColumnCallbacks}
|
||||
ref={gridRef}
|
||||
/>
|
||||
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
|
||||
type Props = AgGridReactProps & {
|
||||
style?: React.CSSProperties;
|
||||
gridRef?: React.Ref<AgGridReact>;
|
||||
storeKey?: string;
|
||||
};
|
||||
|
||||
export const AgGridLazyInternal = lazy(() =>
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
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,8 +1,10 @@
|
||||
import { memo } from 'react';
|
||||
import { BID_COLOR, ASK_COLOR } from './vol-cell';
|
||||
import { addDecimalsFixedFormatNumber } from '@vegaprotocol/utils';
|
||||
import { NumericCell } from './numeric-cell';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
|
||||
const BID_COLOR = theme.colors.vega.green.DEFAULT;
|
||||
const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
|
||||
export interface CumulativeVolProps {
|
||||
ask?: number;
|
||||
bid?: number;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VolCell } from './vol-cell';
|
||||
import * as tailwind from '@vegaprotocol/tailwindcss-config';
|
||||
|
||||
describe('VolCell', () => {
|
||||
const significantPart = '12,345';
|
||||
const decimalPart = '67';
|
||||
const props = {
|
||||
value: 1234567,
|
||||
valueFormatted: `${significantPart}.${decimalPart}`,
|
||||
type: 'ask' as const,
|
||||
testId: 'cell',
|
||||
};
|
||||
|
||||
it('Displays formatted value', () => {
|
||||
render(<VolCell {...props} />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent(
|
||||
props.valueFormatted
|
||||
);
|
||||
expect(screen.getByText(decimalPart)).toBeInTheDocument();
|
||||
expect(screen.getByText(decimalPart)).toHaveClass('opacity-60');
|
||||
});
|
||||
|
||||
it('Displays 0', () => {
|
||||
render(<VolCell {...props} value={0} valueFormatted="0.00" />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent('0.00');
|
||||
});
|
||||
|
||||
it('Displays - if value is not a number', () => {
|
||||
render(<VolCell {...props} value={null} valueFormatted="" />);
|
||||
expect(screen.getByTestId(props.testId)).toHaveTextContent('-');
|
||||
});
|
||||
|
||||
it('renders bid volume bar', () => {
|
||||
render(<VolCell {...props} type="bid" />);
|
||||
expect(screen.getByTestId('vol-bar')).toHaveClass('left-0'); // renders bid bars from the left
|
||||
expect(screen.getByTestId('vol-bar')).toHaveStyle({
|
||||
backgroundColor: tailwind.theme.colors.vega.green.DEFAULT,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders ask volume bar', () => {
|
||||
render(<VolCell {...props} type="ask" />);
|
||||
expect(screen.getByTestId('vol-bar')).toHaveClass('right-0'); // renders ask bars from the right
|
||||
expect(screen.getByTestId('vol-bar')).toHaveStyle({
|
||||
backgroundColor: tailwind.theme.colors.vega.pink.DEFAULT,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import { memo } from 'react';
|
||||
import type { ICellRendererParams } from 'ag-grid-community';
|
||||
import classNames from 'classnames';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
import { NumericCell } from './numeric-cell';
|
||||
|
||||
export interface VolCellProps {
|
||||
value: number | bigint | null | undefined;
|
||||
valueFormatted: string;
|
||||
relativeValue?: number;
|
||||
type: 'ask' | 'bid';
|
||||
testId?: string;
|
||||
}
|
||||
export interface IVolCellProps extends ICellRendererParams {
|
||||
value: number | bigint | null | undefined;
|
||||
valueFormatted: Omit<VolCellProps, 'value'>;
|
||||
}
|
||||
|
||||
export const BID_COLOR = theme.colors.vega.green.DEFAULT;
|
||||
export const ASK_COLOR = theme.colors.vega.pink.DEFAULT;
|
||||
|
||||
export const VolCell = memo(
|
||||
({ value, valueFormatted, relativeValue, type, testId }: VolCellProps) => {
|
||||
if ((!value && value !== 0) || isNaN(Number(value))) {
|
||||
return <div data-testid={testId || 'vol'}>-</div>;
|
||||
}
|
||||
return (
|
||||
<div className="relative" data-testid={testId || 'vol'}>
|
||||
<div
|
||||
data-testid="vol-bar"
|
||||
className={classNames(
|
||||
'h-full absolute top-0 opacity-40 dark:opacity-100',
|
||||
{
|
||||
'left-0': type === 'bid',
|
||||
'right-0': type === 'ask',
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
width: relativeValue ? `${relativeValue}%` : '0%',
|
||||
backgroundColor: type === 'bid' ? BID_COLOR : ASK_COLOR,
|
||||
opacity: 0.6,
|
||||
}}
|
||||
/>
|
||||
<NumericCell value={value} valueFormatted={valueFormatted} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
VolCell.displayName = 'VolCell';
|
||||
@@ -7,6 +7,6 @@ export const COL_DEFS = {
|
||||
minWidth: 45,
|
||||
maxWidth: 45,
|
||||
type: 'rightAligned',
|
||||
pinned: 'right',
|
||||
pinned: 'right' as const,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,18 +4,17 @@ import type {
|
||||
ValueFormatterParams,
|
||||
ValueGetterParams,
|
||||
} from 'ag-grid-community';
|
||||
import type { IDatasource, IGetRowsParams, RowNode } from 'ag-grid-community';
|
||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
||||
import type { AgGridReactProps } from 'ag-grid-react';
|
||||
|
||||
type Field = string | readonly string[];
|
||||
|
||||
type RowHelper<TObj, TRow, TField extends Field> = Omit<
|
||||
TObj,
|
||||
'data' | 'value' | 'node'
|
||||
'data' | 'value'
|
||||
> & {
|
||||
data?: TRow;
|
||||
value?: Get<TRow, TField>;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
@@ -24,12 +23,8 @@ export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
TField
|
||||
>;
|
||||
|
||||
export type VegaValueGetterParams<TRow> = Omit<
|
||||
ValueGetterParams,
|
||||
'data' | 'node'
|
||||
> & {
|
||||
export type VegaValueGetterParams<TRow> = Omit<ValueGetterParams, 'data'> & {
|
||||
data?: TRow;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
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]
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,185 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
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)),
|
||||
asset.decimals
|
||||
market.decimalPlaces
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -133,7 +133,6 @@ export const DealTicket = ({
|
||||
normalizedOrder?.size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces,
|
||||
asset.decimals,
|
||||
]);
|
||||
|
||||
const feeEstimate = useEstimateFees(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
truncateByChars,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
@@ -21,53 +21,46 @@ export const DepositsTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<DepositFieldsFragment>
|
||||
>((props, ref) => {
|
||||
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={({
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: 'Asset', field: 'asset.symbol' },
|
||||
{
|
||||
headerName: 'Amount',
|
||||
field: 'amount',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => {
|
||||
return isNumeric(value) && data
|
||||
? addDecimalsFormatNumber(value, data.asset.decimals)
|
||||
: null;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Created at"
|
||||
field="createdTimestamp"
|
||||
valueFormatter={({
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Created at',
|
||||
field: 'createdTimestamp',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
DepositFieldsFragment,
|
||||
'createdTimestamp'
|
||||
>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Status"
|
||||
field="status"
|
||||
valueFormatter={({
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Status',
|
||||
field: 'status',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => {
|
||||
return value ? DepositStatusMapping[value] : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName="Tx hash"
|
||||
field="txHash"
|
||||
cellRenderer={({
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: 'Tx hash',
|
||||
field: 'txHash',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
|
||||
@@ -78,9 +71,19 @@ export const DepositsTable = forwardRef<
|
||||
{truncateByChars(value)}
|
||||
</EtherscanLink>
|
||||
);
|
||||
}}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
columnDefs={columnDefs}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -301,6 +301,25 @@ 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;
|
||||
|
||||
@@ -269,34 +269,96 @@ const testSubscription = (client: Client) => {
|
||||
* here to appease the environment store interface
|
||||
*/
|
||||
function compileEnvVars() {
|
||||
const VEGA_ENV = process.env['NX_VEGA_ENV'] as Networks;
|
||||
const VEGA_ENV = windowOrDefault(
|
||||
'VEGA_ENV',
|
||||
process.env['NX_VEGA_ENV']
|
||||
) as Networks;
|
||||
const env: Environment = {
|
||||
VEGA_URL: process.env['NX_VEGA_URL'],
|
||||
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
|
||||
VEGA_ENV,
|
||||
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']),
|
||||
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'])
|
||||
),
|
||||
ETHEREUM_PROVIDER_URL: getEthereumProviderUrl(
|
||||
VEGA_ENV,
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL']
|
||||
windowOrDefault(
|
||||
'ETHEREUM_PROVIDER_URL',
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL']
|
||||
)
|
||||
),
|
||||
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'],
|
||||
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']),
|
||||
};
|
||||
|
||||
return env;
|
||||
@@ -341,3 +403,14 @@ 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,6 +5,7 @@ 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;
|
||||
@@ -38,7 +39,7 @@ export const useNodeHealth = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
|
||||
if (!isTestEnv() && window.location.hostname !== 'localhost') {
|
||||
startPolling(POLL_INTERVAL);
|
||||
}
|
||||
}, [error, startPolling, stopPolling]);
|
||||
|
||||
@@ -52,6 +52,7 @@ 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,3 +1,3 @@
|
||||
export * from './lib/fills-container';
|
||||
export * from './lib/fills-manager';
|
||||
export * from './lib/fills-data-provider';
|
||||
export * from './lib/__generated__/Fills';
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } 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;
|
||||
storeKey?: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}
|
||||
|
||||
export const FillsManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
storeKey,
|
||||
gridProps,
|
||||
}: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
|
||||
@@ -38,9 +38,6 @@ export const FillsManager = ({
|
||||
},
|
||||
variables: { filter },
|
||||
});
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
});
|
||||
|
||||
return (
|
||||
<FillsTable
|
||||
@@ -48,9 +45,8 @@ export const FillsManager = ({
|
||||
rowData={data}
|
||||
partyId={partyId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
{...gridProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import type {
|
||||
AgGridReact,
|
||||
AgGridReactProps,
|
||||
AgReactUiProps,
|
||||
} from 'ag-grid-react';
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
import type { ITooltipParams, ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
positiveClassNames,
|
||||
@@ -39,34 +39,23 @@ 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) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
tooltipHideDelay={2000}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.name"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
type="rightAligned"
|
||||
field="size"
|
||||
cellClassRules={{
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.name',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
type: 'rightAligned',
|
||||
field: 'size',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: Trade }) => {
|
||||
const partySide = getPartySide(data, partyId);
|
||||
return partySide === 'buyer';
|
||||
@@ -75,48 +64,47 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
const partySide = getPartySide(data, partyId);
|
||||
return partySide === 'seller';
|
||||
},
|
||||
}}
|
||||
valueFormatter={formatSize(partyId)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Price')}
|
||||
field="price"
|
||||
valueFormatter={formatPrice}
|
||||
type="rightAligned"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Notional')}
|
||||
field="price"
|
||||
valueFormatter={formatTotal}
|
||||
type="rightAligned"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Role')}
|
||||
field="aggressor"
|
||||
valueFormatter={formatRole(partyId)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Fee')}
|
||||
field="market.tradableInstrument.instrument.product"
|
||||
valueFormatter={formatFee(partyId)}
|
||||
type="rightAligned"
|
||||
tooltipField="market.tradableInstrument.instrument.product"
|
||||
tooltipComponent={FeesBreakdownTooltip}
|
||||
tooltipComponentParams={{ partyId }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Date')}
|
||||
field="createdAt"
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: formatSize(partyId),
|
||||
},
|
||||
{
|
||||
headerName: t('Price'),
|
||||
field: 'price',
|
||||
valueFormatter: formatPrice,
|
||||
type: 'rightAligned',
|
||||
},
|
||||
{
|
||||
headerName: t('Notional'),
|
||||
field: 'price',
|
||||
valueFormatter: formatTotal,
|
||||
type: 'rightAligned',
|
||||
},
|
||||
{
|
||||
headerName: t('Role'),
|
||||
field: 'aggressor',
|
||||
valueFormatter: formatRole(partyId),
|
||||
},
|
||||
{
|
||||
headerName: t('Fee'),
|
||||
field: 'market.tradableInstrument.instrument.product',
|
||||
valueFormatter: formatFee(partyId),
|
||||
type: 'rightAligned',
|
||||
tooltipField: 'market.tradableInstrument.instrument.product',
|
||||
tooltipComponent: FeesBreakdownTooltip,
|
||||
tooltipComponentParams: { partyId },
|
||||
},
|
||||
{
|
||||
headerName: t('Date'),
|
||||
field: 'createdAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="fill-actions"
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Trade, 'id'>) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'fill-actions',
|
||||
cellRenderer: ({ data }: VegaICellRendererParams<Trade, 'id'>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<FillActionsDropdown
|
||||
@@ -125,9 +113,25 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
tradeId={data.id}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
...COL_DEFS.actions,
|
||||
},
|
||||
],
|
||||
[onMarketClick, partyId]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
columnDefs={columnDefs}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
tooltipHideDelay={2000}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './lib/ledger-container';
|
||||
export * from './lib/ledger-manager';
|
||||
export * from './lib/__generated__/LedgerEntries';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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} />;
|
||||
};
|
||||
@@ -10,6 +10,7 @@ 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?: {
|
||||
@@ -24,7 +25,13 @@ const defaultFilter = {
|
||||
},
|
||||
};
|
||||
|
||||
export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
export const LedgerManager = ({
|
||||
partyId,
|
||||
gridProps,
|
||||
}: {
|
||||
partyId: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||
|
||||
@@ -33,7 +40,7 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
partyId,
|
||||
dateRange: filter?.vegaTime?.value,
|
||||
pagination: {
|
||||
first: 5000,
|
||||
first: 10,
|
||||
},
|
||||
}),
|
||||
[partyId, filter?.vegaTime?.value]
|
||||
@@ -45,18 +52,23 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
|
||||
skip: !variables.partyId,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
||||
setFilter(updatedFilter);
|
||||
}, []);
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
||||
setFilter(updatedFilter);
|
||||
gridProps.onFilterChanged(event);
|
||||
},
|
||||
[gridProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LedgerTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
onFilterChanged={onFilterChanged}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||
{...gridProps}
|
||||
onFilterChanged={onFilterChanged}
|
||||
/>
|
||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
||||
</div>
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
AccountTypeMapping,
|
||||
DescriptionTransferTypeMapping,
|
||||
TransferTypeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { formatRFC3339, subDays } from 'date-fns';
|
||||
|
||||
export const TransferTooltipCellComponent = ({
|
||||
@@ -47,9 +47,146 @@ 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: 'calc(100% - 50px)' }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
@@ -61,151 +198,9 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
buttons: ['reset'],
|
||||
},
|
||||
}}
|
||||
storeKey="ledgerTable"
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Sender')}
|
||||
field="fromAccountPartyId"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || '')
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Account type')}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: AccountTypeMapping,
|
||||
}}
|
||||
field="fromAccountType"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-'
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketSender.tradableInstrument.instrument.code"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-'}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Receiver')}
|
||||
field="toAccountPartyId"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || '')
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Account type')}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: AccountTypeMapping,
|
||||
}}
|
||||
field="toAccountType"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-'
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketReceiver.tradableInstrument.instrument.code"
|
||||
cellRenderer={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-'}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Transfer type')}
|
||||
field="transferType"
|
||||
tooltipField="transferType"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: TransferTypeMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : ''
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Quantity')}
|
||||
field="quantity"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Asset')}
|
||||
field="assetId"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || value
|
||||
}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Sender account balance')}
|
||||
field="fromAccountBalance"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Receiver account balance')}
|
||||
field="toAccountBalance"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: value;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Vega time')}
|
||||
field="vegaTime"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
|
||||
}
|
||||
filterParams={dateRangeFilterParams}
|
||||
filter={DateRangeFilter}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Liquidity Provisions
|
||||
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
+3
-2
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
|
||||
export type LiquidityProvisionsQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
|
||||
export type LiquidityProvisionsUpdateSubscriptionVariables = Types.Exact<{
|
||||
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
@@ -31,6 +31,7 @@ export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: {
|
||||
|
||||
export const LiquidityProvisionFieldsFragmentDoc = gql`
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
@@ -38,8 +38,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas?.forEach((delta) => {
|
||||
const id = delta.id;
|
||||
const index = draft.findIndex((a) => delta.id === id);
|
||||
const index = draft.findIndex((a) => delta.id === a.id);
|
||||
if (index !== -1) {
|
||||
draft[index].commitmentAmount = delta.commitmentAmount;
|
||||
draft[index].fee = delta.fee;
|
||||
@@ -47,6 +46,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
draft[index].status = delta.status;
|
||||
} else {
|
||||
draft.unshift({
|
||||
id: delta.id,
|
||||
commitmentAmount: delta.commitmentAmount,
|
||||
fee: delta.fee,
|
||||
status: delta.status,
|
||||
|
||||
@@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => `${data.party.id}-${data.status}`}
|
||||
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
@@ -197,10 +197,9 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
}}
|
||||
storeKey="liquidityProvisionTable"
|
||||
{...props}
|
||||
columnDefs={colDefs}
|
||||
></AgGrid>
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -67,6 +67,7 @@ export const liquidityProviderFeeShareQuery = (
|
||||
|
||||
export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
|
||||
{
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
party: {
|
||||
id: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
accountsConnection: {
|
||||
@@ -92,6 +93,7 @@ export const liquidityFields: LiquidityProvisionFieldsFragment[] = [
|
||||
__typename: 'LiquidityProvision',
|
||||
},
|
||||
{
|
||||
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
party: {
|
||||
id: 'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
accountsConnection: {
|
||||
|
||||
@@ -61,7 +61,6 @@ export const MarketListTable = forwardRef<
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={{ PriceFlashCell, MarketName }}
|
||||
storeKey="allMarkets"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
import { marketDataErrorPolicyGuard } from '@vegaprotocol/data-provider';
|
||||
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { MarketsDataQuery } from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketsDataQuery,
|
||||
MarketsDataQueryVariables,
|
||||
} from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
} from './__generated__/market-data';
|
||||
import { MarketDataUpdateDocument } from './__generated__/market-data';
|
||||
import { MarketsDataDocument } from './__generated__/markets-data';
|
||||
import type { MarketData } from './market-data-provider';
|
||||
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] =>
|
||||
responseData?.marketsConnection?.edges
|
||||
.filter((edge) => edge.node.data)
|
||||
.map((edge) => edge.node.data as MarketData) || null;
|
||||
.map((edge) => edge.node.data as MarketData) || [];
|
||||
|
||||
export const mapMarketDataUpdateToMarketData = (
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
): MarketData => {
|
||||
const { marketId, __typename, ...marketData } = delta;
|
||||
return { ...marketData, market: { id: marketId } };
|
||||
};
|
||||
|
||||
const update = (
|
||||
data: MarketData[] | null,
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
) => {
|
||||
const updatedData = data ? [...data] : [];
|
||||
const item = mapMarketDataUpdateToMarketData(delta);
|
||||
const index = updatedData.findIndex(
|
||||
(data) => data.market.id === item.market.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
updatedData[index] = { ...updatedData[index], ...item };
|
||||
} else {
|
||||
updatedData.push(item);
|
||||
}
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const getDelta = (
|
||||
subscriptionData: MarketDataUpdateSubscription
|
||||
): MarketDataUpdateFieldsFragment => subscriptionData.marketsData[0];
|
||||
|
||||
export const marketsDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
@@ -19,3 +56,25 @@ export const marketsDataProvider = makeDataProvider<
|
||||
getData,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
});
|
||||
|
||||
type Variables = { marketIds: string[] };
|
||||
|
||||
export const marketsLiveDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
MarketData[],
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
Variables,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
MarketsDataQueryVariables
|
||||
>({
|
||||
query: MarketsDataDocument,
|
||||
subscriptionQuery: MarketDataUpdateDocument,
|
||||
getData,
|
||||
getDelta,
|
||||
update,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
getQueryVariables: () => ({}),
|
||||
getSubscriptionVariables: ({ marketIds }: Variables) =>
|
||||
marketIds.map((marketId) => ({ marketId })),
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user