chore: fix data and no data message update issues in trading data grids (#2620)

This commit is contained in:
Bartłomiej Głownia 2023-01-17 16:01:24 +01:00 committed by GitHub
parent c70ec7676e
commit 1a6266e2ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 224 additions and 317 deletions

View File

@ -33,17 +33,16 @@ const AccountsManager = () => {
update, update,
variables, variables,
}); });
const getRows = async ({ const getRows = useCallback(
successCallback, async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
startRow,
endRow,
}: IGetRowsParams) => {
const rowsThisBlock = dataRef.current const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow) ? dataRef.current.slice(startRow, endRow)
: []; : [];
const lastRow = dataRef.current ? dataRef.current.length : 0; const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow); successCallback(rowsThisBlock, lastRow);
}; },
[]
);
const { columnDefs, defaultColDef } = useAccountColumnDefinitions(); const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
return ( return (
<> <>

View File

@ -13,7 +13,7 @@ import useColumnDefinitions from './use-column-definitions';
const Positions = () => { const Positions = () => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const { partyId } = useOutletContext<{ partyId: string }>(); const { partyId } = useOutletContext<{ partyId: string }>();
const { data, error, loading } = usePositionsData(partyId, gridRef); const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
const { columnDefs, defaultColDef } = useColumnDefinitions(); const { columnDefs, defaultColDef } = useColumnDefinitions();
return ( return (
<AsyncRenderer <AsyncRenderer
@ -29,7 +29,8 @@ const Positions = () => {
columnDefs={columnDefs} columnDefs={columnDefs}
defaultColDef={defaultColDef} defaultColDef={defaultColDef}
getRowId={getRowId} getRowId={getRowId}
rowData={data || undefined} rowModelType="infinite"
datasource={{ getRows }}
components={{ PriceFlashCell }} components={{ PriceFlashCell }}
/> />
</AsyncRenderer> </AsyncRenderer>

View File

@ -12,6 +12,7 @@ import {
t, t,
useDataProvider, useDataProvider,
useNetworkParams, useNetworkParams,
updateGridData,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {
@ -22,21 +23,18 @@ import {
Indicator, Indicator,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { Header, HeaderStat } from '../../components/header'; import { Header, HeaderStat } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
import type { LiquidityProvisionData } from '@vegaprotocol/liquidity'; import type { LiquidityProvisionData } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router'; import { Links, Routes } from '../../pages/client-router';
import type {
MarketData, import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
MarketDataUpdateFieldsFragment,
MarketDealTicket,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
export const Liquidity = () => { export const Liquidity = () => {
const params = useParams(); const params = useParams();
@ -44,86 +42,42 @@ export const Liquidity = () => {
return <LiquidityViewContainer marketId={marketId} />; return <LiquidityViewContainer marketId={marketId} />;
}; };
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: useMemo(() => ({ marketId }), [marketId]),
});
useEffect(() => {
const interval = setInterval(reload, 10000);
return () => clearInterval(interval);
}, [reload]);
};
export const LiquidityContainer = ({ export const LiquidityContainer = ({
marketId, marketId,
}: { }: {
marketId: string | undefined; marketId: string | undefined;
}) => { }) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const [market, setMarket] = useState<MarketDealTicket | null>(null); const market = useMarket(marketId);
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data: marketProvision } = useDataProvider<
SingleMarketFieldsFragment,
never
>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const updateMarket = useCallback(
({ data: marketData }: { data: MarketData | null }) => {
if (marketData) {
setMarket({
...marketProvision,
data: marketData,
} as MarketDealTicket);
}
return true;
},
[marketProvision]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update: updateMarket,
variables,
skip: !marketId || !marketProvision,
});
const dataRef = useRef<LiquidityProvisionData[] | null>(null); const dataRef = useRef<LiquidityProvisionData[] | null>(null);
const { reload } = useDataProvider({ // To be removed when liquidityProvision subscriptions are working
dataProvider: liquidityProvisionsDataProvider, useReloadLiquidityData(marketId);
variables,
});
const update = useCallback( const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => { ({ data }: { data: LiquidityProvisionData[] | null }) => {
if (!gridRef.current?.api) { return updateGridData(dataRef, data, gridRef);
return false;
}
if (dataRef.current?.length) {
dataRef.current = data;
gridRef.current.api.refreshInfiniteCache();
return true;
}
return false;
}, },
[gridRef] [gridRef]
); );
const { const { data, loading, error } = useDataProvider({
data: liquidityProviders,
loading,
error,
} = useDataProvider({
dataProvider: lpAggregatedDataProvider, dataProvider: lpAggregatedDataProvider,
update, update,
variables: useMemo(() => ({ marketId }), [marketId]), variables: useMemo(() => ({ marketId }), [marketId]),
}); });
// To be removed when liquidityProvision subscriptions are working
useEffect(() => {
const interval = setInterval(reload, 10000);
return () => clearInterval(interval);
}, [reload]);
const assetDecimalPlaces = const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0; market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol = const symbol =
@ -133,28 +87,38 @@ export const LiquidityContainer = ({
NetworkParams.market_liquidity_stakeToCcyVolume, NetworkParams.market_liquidity_stakeToCcyVolume,
]); ]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume; const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
const filteredEdges = useMemo(
() => const getRows = useCallback(
liquidityProviders?.filter((e) => async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
[ const rowsThisBlock = dataRef.current
Schema.LiquidityProvisionStatus.STATUS_ACTIVE, ? dataRef.current.slice(startRow, endRow)
Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED, : [];
Schema.LiquidityProvisionStatus.STATUS_PENDING, const lastRow = dataRef.current ? dataRef.current.length : 0;
].includes(e.status) successCallback(rowsThisBlock, lastRow);
), },
[liquidityProviders] []
); );
return ( return (
<AsyncRenderer loading={loading} error={error} data={filteredEdges}> <div className="h-full relative">
<LiquidityTable <LiquidityTable
ref={gridRef} ref={gridRef}
data={filteredEdges} datasource={{ getRows }}
rowModelType="infinite"
symbol={symbol} symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces} assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume} stakeToCcyVolume={stakeToCcyVolume}
/> />
</AsyncRenderer> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No liquidity provisions')}
noDataCondition={(data) => !data?.length}
/>
</div>
</div>
); );
}; };
@ -165,48 +129,13 @@ export const LiquidityViewContainer = ({
}) => { }) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const [market, setMarket] = useState<MarketDealTicket | null>(null); const market = useMarket(marketId);
const variables = useMemo( const marketData = useStaticMarketData(marketId);
() => ({
marketId: marketId,
}),
[marketId]
);
const { data: marketProvision } = useDataProvider<
SingleMarketFieldsFragment,
never
>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const updateMarket = useCallback(
({ data: marketData }: { data: MarketData | null }) => {
if (marketData) {
setMarket({
...marketProvision,
data: marketData,
} as MarketDealTicket);
}
return true;
},
[marketProvision]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update: updateMarket,
variables,
skip: !marketId || !marketProvision,
});
const dataRef = useRef<LiquidityProvisionData[] | null>(null); const dataRef = useRef<LiquidityProvisionData[] | null>(null);
const { reload } = useDataProvider({ // To be removed when liquidityProvision subscriptions are working
dataProvider: liquidityProvisionsDataProvider, useReloadLiquidityData(marketId);
variables: useMemo(() => ({ marketId }), [marketId]),
});
const update = useCallback( const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => { ({ data }: { data: LiquidityProvisionData[] | null }) => {
@ -233,14 +162,8 @@ export const LiquidityViewContainer = ({
variables: useMemo(() => ({ marketId }), [marketId]), variables: useMemo(() => ({ marketId }), [marketId]),
}); });
// To be removed when liquidityProvision subscriptions are working const targetStake = marketData?.targetStake;
useEffect(() => { const suppliedStake = marketData?.suppliedStake;
const interval = setInterval(reload, 10000);
return () => clearInterval(interval);
}, [reload]);
const targetStake = market?.data?.targetStake;
const suppliedStake = market?.data?.suppliedStake;
const assetDecimalPlaces = const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0; market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol = const symbol =
@ -359,7 +282,7 @@ export const LiquidityViewContainer = ({
{myLpEdges && ( {myLpEdges && (
<LiquidityTable <LiquidityTable
ref={gridRef} ref={gridRef}
data={myLpEdges} rowData={myLpEdges}
symbol={symbol} symbol={symbol}
stakeToCcyVolume={stakeToCcyVolume} stakeToCcyVolume={stakeToCcyVolume}
assetDecimalPlaces={assetDecimalPlaces} assetDecimalPlaces={assetDecimalPlaces}
@ -370,7 +293,7 @@ export const LiquidityViewContainer = ({
{activeEdges && ( {activeEdges && (
<LiquidityTable <LiquidityTable
ref={gridRef} ref={gridRef}
data={activeEdges} rowData={activeEdges}
symbol={symbol} symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces} assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume} stakeToCcyVolume={stakeToCcyVolume}
@ -382,7 +305,7 @@ export const LiquidityViewContainer = ({
{inactiveEdges && ( {inactiveEdges && (
<LiquidityTable <LiquidityTable
ref={gridRef} ref={gridRef}
data={inactiveEdges} rowData={inactiveEdges}
symbol={symbol} symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces} assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume} stakeToCcyVolume={stakeToCcyVolume}

View File

@ -9,16 +9,21 @@ export const DepositsContainer = () => {
return ( return (
<div className="h-full grid grid-rows-[1fr,min-content]"> <div className="h-full grid grid-rows-[1fr,min-content]">
<div> <div className="h-full relative">
<DepositsTable
rowData={deposits || []}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer
data={deposits} data={deposits}
loading={loading} loading={loading}
error={error} error={error}
render={(data) => { noDataCondition={(data) => !(data && data.length)}
return <DepositsTable deposits={data} />; noDataMessage={t('No deposits')}
}}
/> />
</div> </div>
</div>
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2"> <div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button <Button
size="sm" size="sm"

View File

@ -1,4 +1,8 @@
import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import {
t,
useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { useRef, useMemo, useCallback, memo } from 'react'; import { useRef, useMemo, useCallback, memo } from 'react';
@ -25,10 +29,7 @@ export const AccountManager = ({
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useCallback( const update = useCallback(
({ data }: { data: AccountFields[] | null }) => { ({ data }: { data: AccountFields[] | null }) => {
const isEmpty = !dataRef.current?.length; return updateGridData(dataRef, data, gridRef);
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return Boolean((isEmpty && !data?.length) || (!isEmpty && data?.length));
}, },
[gridRef] [gridRef]
); );

View File

@ -1,3 +1,4 @@
import { forwardRef } from 'react';
import { AgGridColumn } from 'ag-grid-react'; import { AgGridColumn } from 'ag-grid-react';
import { import {
t, t,
@ -9,25 +10,27 @@ import {
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
VegaValueFormatterParams, VegaValueFormatterParams,
TypedDataAgGrid,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridDynamic as AgGrid, Link } from '@vegaprotocol/ui-toolkit'; import { AgGridDynamic as AgGrid, Link } from '@vegaprotocol/ui-toolkit';
import type { DepositFieldsFragment } from './__generated__/Deposit'; import type { DepositFieldsFragment } from './__generated__/Deposit';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { DepositStatusMapping } from '@vegaprotocol/types'; import { DepositStatusMapping } from '@vegaprotocol/types';
export interface DepositsTableProps { export const DepositsTable = forwardRef<
deposits: DepositFieldsFragment[]; AgGridReact,
} TypedDataAgGrid<DepositFieldsFragment>
>((props, ref) => {
export const DepositsTable = ({ deposits }: DepositsTableProps) => {
const { ETHERSCAN_URL } = useEnvironment(); const { ETHERSCAN_URL } = useEnvironment();
return ( return (
<AgGrid <AgGrid
rowData={deposits} ref={ref}
overlayNoRowsTemplate={t('No deposits')} overlayNoRowsTemplate={t('No deposits')}
defaultColDef={{ flex: 1, resizable: true }} defaultColDef={{ flex: 1, resizable: true }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
suppressCellFocus={true} suppressCellFocus={true}
{...props}
> >
<AgGridColumn headerName="Asset" field="asset.symbol" /> <AgGridColumn headerName="Asset" field="asset.symbol" />
<AgGridColumn <AgGridColumn
@ -84,4 +87,4 @@ export const DepositsTable = ({ deposits }: DepositsTableProps) => {
/> />
</AgGrid> </AgGrid>
); );
}; });

View File

@ -4,6 +4,7 @@ import { useCallback, useMemo, useRef } from 'react';
import { import {
makeInfiniteScrollGetRows, makeInfiniteScrollGetRows,
useDataProvider, useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { Trade, TradeEdge } from './fills-data-provider'; import type { Trade, TradeEdge } from './fills-data-provider';
import { fillsWithMarketProvider } from './fills-data-provider'; import { fillsWithMarketProvider } from './fills-data-provider';
@ -53,13 +54,7 @@ export const useFillsList = ({
).length; ).length;
} }
} }
dataRef.current = data; return updateGridData(dataRef, data, gridRef);
const avoidRerender = !!(
(dataRef.current?.length && data?.length) ||
(!dataRef.current?.length && !data?.length)
);
gridRef.current?.api?.refreshInfiniteCache();
return avoidRerender;
} }
dataRef.current = data; dataRef.current = data;
return false; return false;
@ -75,11 +70,10 @@ export const useFillsList = ({
data: (TradeEdge | null)[] | null; data: (TradeEdge | null)[] | null;
totalCount?: number; totalCount?: number;
}) => { }) => {
dataRef.current = data;
totalCountRef.current = totalCount; totalCountRef.current = totalCount;
return true; return updateGridData(dataRef, data, gridRef);
}, },
[] [gridRef]
); );
const variables = useMemo(() => ({ partyId, marketId }), [partyId, marketId]); const variables = useMemo(() => ({ partyId, marketId }), [partyId, marketId]);

View File

@ -9,6 +9,7 @@ import {
makeDataProvider, makeDataProvider,
makeDerivedDataProvider, makeDerivedDataProvider,
useDataProvider, useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type * as Schema from '@vegaprotocol/types'; import type * as Schema from '@vegaprotocol/types';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
@ -158,12 +159,7 @@ export const useLedgerEntriesDataProvider = ({
const update = useCallback( const update = useCallback(
({ data }: { data: (AggregatedLedgerEntriesEdge | null)[] | null }) => { ({ data }: { data: (AggregatedLedgerEntriesEdge | null)[] | null }) => {
dataRef.current = data; return updateGridData(dataRef, data, gridRef);
const rerender =
(!dataRef.current?.length && data?.length) ||
(dataRef.current?.length && !data?.length);
gridRef.current?.api?.refreshInfiniteCache();
return !rerender;
}, },
[gridRef] [gridRef]
); );
@ -178,9 +174,9 @@ export const useLedgerEntriesDataProvider = ({
}) => { }) => {
dataRef.current = data; dataRef.current = data;
totalCountRef.current = totalCount; totalCountRef.current = totalCount;
return true; return updateGridData(dataRef, data, gridRef);
}, },
[] [gridRef]
); );
const { data, error, loading, load, totalCount } = useDataProvider({ const { data, error, loading, load, totalCount } = useDataProvider({

View File

@ -22,7 +22,7 @@ export const ledgerEntriesQuery = (
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDE3OjI3OjU2LjczNDM2NFoifQ==', 'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDE3OjI3OjU2LjczNDM2NFoifQ==',
endCursor: endCursor:
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDEzOjExOjE2LjU0NjM2M1oifQ==', 'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDEzOjExOjE2LjU0NjM2M1oifQ==',
hasNextPage: true, hasNextPage: false,
hasPreviousPage: false, hasPreviousPage: false,
__typename: 'PageInfo', __typename: 'PageInfo',
}, },

View File

@ -152,10 +152,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
export const lpAggregatedDataProvider = makeDerivedDataProvider( export const lpAggregatedDataProvider = makeDerivedDataProvider(
[ [
(callback, client, variables) => liquidityProvisionsDataProvider,
liquidityProvisionsDataProvider(callback, client, {
marketId: variables?.marketId,
}),
marketLiquidityDataProvider, marketLiquidityDataProvider,
liquidityFeeShareDataProvider, liquidityFeeShareDataProvider,
], ],
@ -177,9 +174,12 @@ export const getLiquidityProvision = (
marketLiquidity: MarketLpQuery, marketLiquidity: MarketLpQuery,
liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[] liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[]
): LiquidityProvisionData[] => { ): LiquidityProvisionData[] => {
return liquidityProvisions.map((lp) => { return liquidityProvisions
.map((lp) => {
const market = marketLiquidity?.market; const market = marketLiquidity?.market;
const feeShare = liquidityFeeShare.find((f) => f.party.id === lp.party.id); const feeShare = liquidityFeeShare.find(
(f) => f.party.id === lp.party.id
);
if (!feeShare) return lp; if (!feeShare) return lp;
const accounts = compact(lp.party.accountsConnection?.edges).map( const accounts = compact(lp.party.accountsConnection?.edges).map(
(e) => e.node (e) => e.node
@ -199,10 +199,18 @@ export const getLiquidityProvision = (
averageEntryValuation: feeShare?.averageEntryValuation, averageEntryValuation: feeShare?.averageEntryValuation,
equityLikeShare: feeShare?.equityLikeShare, equityLikeShare: feeShare?.equityLikeShare,
assetDecimalPlaces: assetDecimalPlaces:
market?.tradableInstrument.instrument.product.settlementAsset.decimals, market?.tradableInstrument.instrument.product.settlementAsset
.decimals,
balance, balance,
}; };
}); })
.filter((e) =>
[
Schema.LiquidityProvisionStatus.STATUS_ACTIVE,
Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED,
Schema.LiquidityProvisionStatus.STATUS_PENDING,
].includes(e.status)
);
}; };
export interface LiquidityProvisionData export interface LiquidityProvisionData

View File

@ -22,7 +22,7 @@ describe('LiquidityTable', () => {
it('should render successfully', async () => { it('should render successfully', async () => {
await act(async () => { await act(async () => {
const { baseElement } = render( const { baseElement } = render(
<LiquidityTable data={[]} stakeToCcyVolume={'1'} /> <LiquidityTable rowData={[]} stakeToCcyVolume={'1'} />
); );
expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
@ -30,7 +30,9 @@ describe('LiquidityTable', () => {
it('should render correct columns', async () => { it('should render correct columns', async () => {
await act(async () => { await act(async () => {
render(<LiquidityTable data={singleRowData} stakeToCcyVolume={'0.3'} />); render(
<LiquidityTable rowData={singleRowData} stakeToCcyVolume={'0.3'} />
);
}); });
const headers = await screen.getAllByRole('columnheader'); const headers = await screen.getAllByRole('columnheader');

View File

@ -5,7 +5,10 @@ import {
getDateTimeFormat, getDateTimeFormat,
t, t,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit'; import type {
VegaValueFormatterParams,
TypedDataAgGrid,
} from '@vegaprotocol/ui-toolkit';
import { import {
AgGridDynamic as AgGrid, AgGridDynamic as AgGrid,
TooltipCellComponent, TooltipCellComponent,
@ -30,15 +33,15 @@ const dateValueFormatter = ({ value }: { value?: string | null }) => {
return getDateTimeFormat().format(new Date(value)); return getDateTimeFormat().format(new Date(value));
}; };
export interface LiquidityTableProps { export interface LiquidityTableProps
data?: LiquidityProvisionData[]; extends TypedDataAgGrid<LiquidityProvisionData> {
symbol?: string; symbol?: string;
assetDecimalPlaces?: number; assetDecimalPlaces?: number;
stakeToCcyVolume: string | null; stakeToCcyVolume: string | null;
} }
export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>( export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
({ data, symbol = '', assetDecimalPlaces, stakeToCcyVolume }, ref) => { ({ symbol = '', assetDecimalPlaces, stakeToCcyVolume, ...props }, ref) => {
const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => { const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => {
if (!value) return '-'; if (!value) return '-';
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`; return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`;
@ -51,7 +54,6 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0, 5)}`; return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0, 5)}`;
}; };
if (!data) return null;
return ( return (
<AgGrid <AgGrid
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
@ -66,7 +68,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
sortable: true, sortable: true,
}} }}
rowData={data} {...props}
> >
<AgGridColumn <AgGridColumn
headerName={t('Party')} headerName={t('Party')}

View File

@ -128,9 +128,10 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const marketDataUpdate = useCallback( const marketDataUpdate = useCallback(
({ data }: { data: MarketData | null }) => { ({ data }: { data: MarketData | null }) => {
marketDataRef.current = data; marketDataRef.current = data;
updateDepthData();
return true; return true;
}, },
[] [updateDepthData]
); );
const { const {

View File

@ -1,3 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table'; import { MarketListTable } from './market-list-table';
import { useDataProvider } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/react-helpers';
@ -15,7 +16,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
}); });
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <div className="h-full relative">
<MarketListTable <MarketListTable
rowData={data} rowData={data}
onRowClicked={(rowEvent: RowClickedEvent) => { onRowClicked={(rowEvent: RowClickedEvent) => {
@ -29,6 +30,14 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
onSelect((data as MarketWithData).id); onSelect((data as MarketWithData).id);
}} }}
/> />
</AsyncRenderer> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No markets')}
/>
</div>
</div>
); );
}; };

View File

@ -4,6 +4,7 @@ import type { AgGridReact } from 'ag-grid-react';
import { import {
makeInfiniteScrollGetRows, makeInfiniteScrollGetRows,
useDataProvider, useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider'; import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
import type { import type {
@ -99,13 +100,7 @@ export const useOrderListData = ({
).length; ).length;
} }
} }
dataRef.current = data; return updateGridData(dataRef, data, gridRef);
totalCountRef.current = totalCount;
const rerender =
(!dataRef.current?.length && data?.length) ||
(dataRef.current?.length && !data?.length);
gridRef.current?.api?.refreshInfiniteCache();
return !rerender;
}, },
[gridRef, scrolledToTop] [gridRef, scrolledToTop]
); );
@ -118,11 +113,10 @@ export const useOrderListData = ({
data: (OrderEdge | null)[] | null; data: (OrderEdge | null)[] | null;
totalCount?: number; totalCount?: number;
}) => { }) => {
dataRef.current = data;
totalCountRef.current = totalCount; totalCountRef.current = totalCount;
return true; return updateGridData(dataRef, data, gridRef);
}, },
[] [gridRef]
); );
const { data, error, loading, load, totalCount } = useDataProvider({ const { data, error, loading, load, totalCount } = useDataProvider({

View File

@ -13,7 +13,7 @@ interface PositionsManagerProps {
export const PositionsManager = ({ partyId }: PositionsManagerProps) => { export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading } = usePositionsData(partyId, gridRef); const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
const { const {
submit, submit,
closingOrder, closingOrder,
@ -26,8 +26,9 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
return ( return (
<div className="h-full relative"> <div className="h-full relative">
<PositionsTable <PositionsTable
rowModelType="infinite"
ref={gridRef} ref={gridRef}
rowData={data} datasource={{ getRows }}
onClose={(position) => submit(position)} onClose={(position) => submit(position)}
noRowsOverlayComponent={() => null} noRowsOverlayComponent={() => null}
/> />

View File

@ -53,22 +53,13 @@ describe('usePositionData Hook', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
const mockApplyTransactions = jest.fn(); const mockRefreshInfiniteCache = jest.fn();
const mockApplyTransactionsAsync = jest.fn();
const mockGetRowNode = jest const mockGetRowNode = jest
.fn() .fn()
.mockImplementation((id: string) => .mockImplementation((id: string) =>
mockData.find((position) => position.marketId === id) mockData.find((position) => position.marketId === id)
); );
const partyId = 'partyId'; const partyId = 'partyId';
const aNewOne = {
marketId: 'market-5',
openVolume: '1',
};
const toRemoveOne = {
marketId: 'market-0',
openVolume: '0',
};
const anUpdatedOne = { const anUpdatedOne = {
marketId: 'market-1', marketId: 'market-1',
openVolume: '1', openVolume: '1',
@ -76,8 +67,7 @@ describe('usePositionData Hook', () => {
const gridRef = { const gridRef = {
current: { current: {
api: { api: {
applyTransaction: mockApplyTransactions, refreshInfiniteCache: mockRefreshInfiniteCache,
applyTransactionAsync: mockApplyTransactionsAsync,
getRowNode: mockGetRowNode, getRowNode: mockGetRowNode,
}, },
} as unknown as AgGridReact, } as unknown as AgGridReact,
@ -87,27 +77,10 @@ describe('usePositionData Hook', () => {
const { result } = renderHook(() => usePositionsData(partyId, gridRef), { const { result } = renderHook(() => usePositionsData(partyId, gridRef), {
wrapper: MockedProvider, wrapper: MockedProvider,
}); });
expect(result.current.data?.length ?? 0).toEqual(3); expect(result.current.data?.length ?? 0).toEqual(5);
}); });
it('should append by sync', async () => { it('should call mockRefreshInfiniteCache', async () => {
renderHook(() => usePositionsData(partyId, gridRef), {
wrapper: MockedProvider,
});
await waitFor(() => {
updateMock({ delta: [aNewOne, toRemoveOne, anUpdatedOne] as Position[] });
});
expect(mockApplyTransactions).toHaveBeenCalledWith({
update: [anUpdatedOne],
add: [aNewOne],
remove: [toRemoveOne],
addIndex: 0,
});
});
it('should append by async', async () => {
renderHook(() => usePositionsData(partyId, gridRef), { renderHook(() => usePositionsData(partyId, gridRef), {
wrapper: MockedProvider, wrapper: MockedProvider,
}); });
@ -115,12 +88,7 @@ describe('usePositionData Hook', () => {
updateMock({ delta: [anUpdatedOne] as Position[] }); updateMock({ delta: [anUpdatedOne] as Position[] });
}); });
expect(mockApplyTransactionsAsync).toHaveBeenCalledWith({ expect(mockRefreshInfiniteCache).toHaveBeenCalledWith();
update: [anUpdatedOne],
add: [],
remove: [],
addIndex: 0,
});
}); });
it('no data should return null', () => { it('no data should return null', () => {

View File

@ -4,7 +4,8 @@ import type { AgGridReact } from 'ag-grid-react';
import type { Position } from './positions-data-providers'; import type { Position } from './positions-data-providers';
import { positionsMetricsProvider } from './positions-data-providers'; import { positionsMetricsProvider } from './positions-data-providers';
import type { PositionsMetricsProviderVariables } from './positions-data-providers'; import type { PositionsMetricsProviderVariables } from './positions-data-providers';
import { useDataProvider } from '@vegaprotocol/react-helpers'; import { useDataProvider, updateGridData } from '@vegaprotocol/react-helpers';
import type { GetRowsParams } from '@vegaprotocol/ui-toolkit';
export const getRowId = ({ data }: { data: Position }) => data.marketId; export const getRowId = ({ data }: { data: Position }) => data.marketId;
@ -18,49 +19,8 @@ export const usePositionsData = (
); );
const dataRef = useRef<Position[] | null>(null); const dataRef = useRef<Position[] | null>(null);
const update = useCallback( const update = useCallback(
({ ({ data }: { data: Position[] | null }) => {
data, return updateGridData(dataRef, data, gridRef);
delta,
}: {
data: Position[] | null;
delta?: Position[] | null;
}) => {
dataRef.current = data;
const update: Position[] = [];
const add: Position[] = [];
const remove: Position[] = [];
if (!gridRef.current?.api) {
return false;
}
(delta || []).forEach((position) => {
const rowNode = gridRef.current?.api.getRowNode(
getRowId({ data: position })
);
if (rowNode) {
if (position.openVolume === '0') {
remove.push(position);
} else {
update.push(position);
}
} else if (position.openVolume !== '0') {
add.push(position);
}
});
if (update.length || add.length || remove.length) {
const rowDataTransaction = {
update,
add,
remove,
addIndex: 0,
};
if (add.length || remove.length) {
gridRef.current.api.applyTransaction(rowDataTransaction);
} else {
gridRef.current.api.applyTransactionAsync(rowDataTransaction);
}
}
return true;
}, },
[gridRef] [gridRef]
); );
@ -69,9 +29,20 @@ export const usePositionsData = (
update, update,
variables, variables,
}); });
const getRows = useCallback(
async ({ successCallback, startRow, endRow }: GetRowsParams<Position>) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
},
[]
);
return { return {
data: data?.filter((position) => position.openVolume !== '0'), data,
error, error,
loading, loading,
getRows,
}; };
}; };

View File

@ -6,6 +6,7 @@ export * from './lib/get-events';
export * from './lib/grid'; export * from './lib/grid';
export * from './lib/i18n'; export * from './lib/i18n';
export * from './lib/pagination'; export * from './lib/pagination';
export * from './lib/ag-grid-update';
export * from './lib/remove-0x'; export * from './lib/remove-0x';
export * from './lib/storage'; export * from './lib/storage';
export * from './lib/time'; export * from './lib/time';

View File

@ -0,0 +1,28 @@
import type { MutableRefObject, RefObject } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
type AnyArray = Array<any> | null; // eslint-disable-line @typescript-eslint/no-explicit-any
export const isXOrWasEmpty = (prev?: AnyArray, curr?: AnyArray) =>
Boolean(Number(!!prev?.length) ^ Number(!!curr?.length));
export const updateGridData = (
dataRef: MutableRefObject<AnyArray>,
data: AnyArray,
gridRef: RefObject<AgGridReact>
) => {
const rerender = isXOrWasEmpty(dataRef.current, data);
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return !rerender;
};
export const makeGetRows =
(dataRef: MutableRefObject<AnyArray>) =>
() =>
async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
};