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,
variables,
});
const getRows = 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);
};
const getRows = useCallback(
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);
},
[]
);
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
return (
<>

View File

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

View File

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

View File

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

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 type { AgGridReact } from 'ag-grid-react';
import { useRef, useMemo, useCallback, memo } from 'react';
@ -25,10 +29,7 @@ export const AccountManager = ({
const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
const isEmpty = !dataRef.current?.length;
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return Boolean((isEmpty && !data?.length) || (!isEmpty && data?.length));
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);

View File

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

View File

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

View File

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

View File

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

View File

@ -152,10 +152,7 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
export const lpAggregatedDataProvider = makeDerivedDataProvider(
[
(callback, client, variables) =>
liquidityProvisionsDataProvider(callback, client, {
marketId: variables?.marketId,
}),
liquidityProvisionsDataProvider,
marketLiquidityDataProvider,
liquidityFeeShareDataProvider,
],
@ -177,32 +174,43 @@ export const getLiquidityProvision = (
marketLiquidity: MarketLpQuery,
liquidityFeeShare: LiquidityProviderFeeShareFieldsFragment[]
): LiquidityProvisionData[] => {
return liquidityProvisions.map((lp) => {
const market = marketLiquidity?.market;
const feeShare = liquidityFeeShare.find((f) => f.party.id === lp.party.id);
if (!feeShare) return lp;
const accounts = compact(lp.party.accountsConnection?.edges).map(
(e) => e.node
return liquidityProvisions
.map((lp) => {
const market = marketLiquidity?.market;
const feeShare = liquidityFeeShare.find(
(f) => f.party.id === lp.party.id
);
if (!feeShare) return lp;
const accounts = compact(lp.party.accountsConnection?.edges).map(
(e) => e.node
);
const bondAccounts = accounts?.filter(
(a) => a?.type === Schema.AccountType.ACCOUNT_TYPE_BOND
);
const balance =
bondAccounts
?.reduce(
(acc, a) => acc.plus(new BigNumber(a.balance ?? 0)),
new BigNumber(0)
)
.toString() || '0';
return {
...lp,
averageEntryValuation: feeShare?.averageEntryValuation,
equityLikeShare: feeShare?.equityLikeShare,
assetDecimalPlaces:
market?.tradableInstrument.instrument.product.settlementAsset
.decimals,
balance,
};
})
.filter((e) =>
[
Schema.LiquidityProvisionStatus.STATUS_ACTIVE,
Schema.LiquidityProvisionStatus.STATUS_UNDEPLOYED,
Schema.LiquidityProvisionStatus.STATUS_PENDING,
].includes(e.status)
);
const bondAccounts = accounts?.filter(
(a) => a?.type === Schema.AccountType.ACCOUNT_TYPE_BOND
);
const balance =
bondAccounts
?.reduce(
(acc, a) => acc.plus(new BigNumber(a.balance ?? 0)),
new BigNumber(0)
)
.toString() || '0';
return {
...lp,
averageEntryValuation: feeShare?.averageEntryValuation,
equityLikeShare: feeShare?.equityLikeShare,
assetDecimalPlaces:
market?.tradableInstrument.instrument.product.settlementAsset.decimals,
balance,
};
});
};
export interface LiquidityProvisionData

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table';
import { useDataProvider } from '@vegaprotocol/react-helpers';
@ -15,7 +16,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
});
return (
<AsyncRenderer loading={loading} error={error} data={data}>
<div className="h-full relative">
<MarketListTable
rowData={data}
onRowClicked={(rowEvent: RowClickedEvent) => {
@ -29,6 +30,14 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
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 {
makeInfiniteScrollGetRows,
useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers';
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
import type {
@ -99,13 +100,7 @@ export const useOrderListData = ({
).length;
}
}
dataRef.current = data;
totalCountRef.current = totalCount;
const rerender =
(!dataRef.current?.length && data?.length) ||
(dataRef.current?.length && !data?.length);
gridRef.current?.api?.refreshInfiniteCache();
return !rerender;
return updateGridData(dataRef, data, gridRef);
},
[gridRef, scrolledToTop]
);
@ -118,11 +113,10 @@ export const useOrderListData = ({
data: (OrderEdge | null)[] | null;
totalCount?: number;
}) => {
dataRef.current = data;
totalCountRef.current = totalCount;
return true;
return updateGridData(dataRef, data, gridRef);
},
[]
[gridRef]
);
const { data, error, loading, load, totalCount } = useDataProvider({

View File

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

View File

@ -53,22 +53,13 @@ describe('usePositionData Hook', () => {
afterEach(() => {
jest.clearAllMocks();
});
const mockApplyTransactions = jest.fn();
const mockApplyTransactionsAsync = jest.fn();
const mockRefreshInfiniteCache = jest.fn();
const mockGetRowNode = jest
.fn()
.mockImplementation((id: string) =>
mockData.find((position) => position.marketId === id)
);
const partyId = 'partyId';
const aNewOne = {
marketId: 'market-5',
openVolume: '1',
};
const toRemoveOne = {
marketId: 'market-0',
openVolume: '0',
};
const anUpdatedOne = {
marketId: 'market-1',
openVolume: '1',
@ -76,8 +67,7 @@ describe('usePositionData Hook', () => {
const gridRef = {
current: {
api: {
applyTransaction: mockApplyTransactions,
applyTransactionAsync: mockApplyTransactionsAsync,
refreshInfiniteCache: mockRefreshInfiniteCache,
getRowNode: mockGetRowNode,
},
} as unknown as AgGridReact,
@ -87,27 +77,10 @@ describe('usePositionData Hook', () => {
const { result } = renderHook(() => usePositionsData(partyId, gridRef), {
wrapper: MockedProvider,
});
expect(result.current.data?.length ?? 0).toEqual(3);
expect(result.current.data?.length ?? 0).toEqual(5);
});
it('should append by sync', 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 () => {
it('should call mockRefreshInfiniteCache', async () => {
renderHook(() => usePositionsData(partyId, gridRef), {
wrapper: MockedProvider,
});
@ -115,12 +88,7 @@ describe('usePositionData Hook', () => {
updateMock({ delta: [anUpdatedOne] as Position[] });
});
expect(mockApplyTransactionsAsync).toHaveBeenCalledWith({
update: [anUpdatedOne],
add: [],
remove: [],
addIndex: 0,
});
expect(mockRefreshInfiniteCache).toHaveBeenCalledWith();
});
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 { positionsMetricsProvider } 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;
@ -18,49 +19,8 @@ export const usePositionsData = (
);
const dataRef = useRef<Position[] | null>(null);
const update = useCallback(
({
data,
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;
({ data }: { data: Position[] | null }) => {
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);
@ -69,9 +29,20 @@ export const usePositionsData = (
update,
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 {
data: data?.filter((position) => position.openVolume !== '0'),
data,
error,
loading,
getRows,
};
};

View File

@ -6,6 +6,7 @@ export * from './lib/get-events';
export * from './lib/grid';
export * from './lib/i18n';
export * from './lib/pagination';
export * from './lib/ag-grid-update';
export * from './lib/remove-0x';
export * from './lib/storage';
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);
};