fix(trading): use ag grid to handle error,loading and no data message (#4012)

This commit is contained in:
Matthew Russell 2023-06-06 00:08:19 -07:00 committed by GitHub
parent 011e4e4cec
commit 0d96c487d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 121 additions and 526 deletions

View File

@ -98,23 +98,14 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
},
},
];
const marketData = marketsDataQuery();
const edges = marketData.marketsConnection?.edges.map((market) => {
const replace =
market.node.data?.market.id === 'market-2' ? null : market.node.data;
return { ...market, node: { ...market.node, data: replace } };
});
const overrides = {
...marketData,
marketsConnection: { ...marketData.marketsConnection, edges },
marketsConnection: { edges: [] },
};
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', overrides, errors);
});
cy.visit('/#/markets/market-0');
cy.get('.pointer-events-none.absolute.inset-0').contains(
'Something went wrong:'
);
cy.get('[data-testid="tab-positions"]').contains('no market data');
});
});

View File

@ -11,14 +11,12 @@ import {
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { updateGridData } from '@vegaprotocol/datagrid';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
AsyncRenderer,
Tab,
Tabs,
Link as UiToolkitLink,
@ -26,14 +24,13 @@ import {
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity';
import type { Filter } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
@ -74,21 +71,12 @@ export const LiquidityContainer = ({
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => {
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);
const { data, loading, error } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
@ -103,36 +91,16 @@ export const LiquidityContainer = ({
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
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 (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
datasource={{ getRows }}
rowModelType="infinite"
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
/>
<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>
);
};

View File

@ -27,7 +27,6 @@ import type { ColDef } from 'ag-grid-community';
import { SettlementDateCell } from './settlement-date-cell';
import { SettlementPriceCell } from './settlement-price-cell';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
type SettlementAsset =
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
@ -55,7 +54,6 @@ export const Closed = () => {
const { pubKey } = useVegaWallet();
const {
data: marketData,
loading,
error,
reload,
} = useDataProvider({
@ -117,21 +115,20 @@ export const Closed = () => {
});
return (
<div className="h-full relative">
<ClosedMarketsDataGrid rowData={rowData} />
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={marketData}
noDataMessage={t('No markets')}
reload={reload}
/>
</div>
<ClosedMarketsDataGrid rowData={rowData} error={error} reload={reload} />
</div>
);
};
const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
const ClosedMarketsDataGrid = ({
rowData,
error,
reload,
}: {
rowData: Row[];
error: Error | undefined;
reload: () => void;
}) => {
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const colDefs = useMemo(() => {
const cols: ColDef[] = [
@ -315,7 +312,7 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
resizable: true,
minWidth: 100,
}}
overlayNoRowsTemplate="No data"
overlayNoRowsTemplate={error ? error.message : t('No markets')}
storeKey="closedMarkets"
/>
);

View File

@ -1,4 +1,4 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
@ -11,7 +11,7 @@ import type { AgGridReact } from 'ag-grid-react';
export const DepositsContainer = () => {
const gridRef = useRef<AgGridReact | null>(null);
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@ -23,21 +23,10 @@ export const DepositsContainer = () => {
<div className="h-full relative">
<DepositsTable
rowData={data || []}
suppressLoadingOverlay
suppressNoRowsOverlay
ref={gridRef}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No deposits')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">

View File

@ -1,4 +1,4 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
useWithdrawalDialog,
@ -11,7 +11,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@ -24,19 +24,8 @@ export const WithdrawalsContainer = () => {
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
suppressLoadingOverlay
suppressNoRowsOverlay
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">

View File

@ -97,7 +97,7 @@ describe('AccountManager', () => {
});
});
it('splash loading should be displayed', async () => {
it('loading should be displayed', async () => {
mockedUseDataProvider.mockImplementation((args) => {
return {
loading: true,
@ -113,15 +113,6 @@ describe('AccountManager', () => {
/>
);
});
await waitFor(() => {
expect(
screen.getByText(
(content, element) =>
Boolean(
element?.className.endsWith('flex items-center justify-center')
) && content.startsWith('Loading')
)
).toBeInTheDocument();
});
expect(await screen.findByText('Loading...')).toBeInTheDocument();
});
});

View File

@ -1,11 +1,9 @@
import { useRef, memo, useCallback, useState, useEffect } from 'react';
import { useRef, memo, useCallback, useState } from 'react';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { RowDataUpdatedEvent } from 'ag-grid-community';
import type { AccountFields } from './accounts-data-provider';
import {
aggregatedAccountsDataProvider,
@ -71,7 +69,6 @@ export const AccountManager = ({
storeKey,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [hasData, setHasData] = useState(Boolean(pinnedAsset));
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
@ -102,7 +99,7 @@ export const AccountManager = ({
},
[gridRef, pinnedAsset]
);
const { data, loading, error, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables: { partyId },
update,
@ -112,45 +109,21 @@ export const AccountManager = ({
disabled: noBottomPlaceholder,
});
useEffect(
() => setHasData(Boolean(pinnedAsset || data?.length)),
[data, pinnedAsset]
);
const onRowDataUpdated = useCallback(
(event: RowDataUpdatedEvent) => {
setHasData(Boolean(pinnedAsset || event.api?.getModel().getRowCount()));
},
[pinnedAsset]
);
return (
<div className="relative h-full">
<AccountTable
ref={gridRef}
rowData={error ? [] : data}
rowData={data}
onClickAsset={onClickAsset}
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
onClickBreakdown={setBreakdownAssetId}
onRowDataUpdated={onRowDataUpdated}
isReadOnly={isReadOnly}
suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
noDataCondition={() => !hasData}
error={error}
loading={loading}
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
reload={reload}
/>
</div>
<Dialog
size="medium"
open={Boolean(breakdownAssetId)}

View File

@ -1,6 +1,7 @@
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';
@ -23,6 +24,8 @@ export const AgGridThemed = ({
rowHeight: 22,
headerHeight: 22,
enableCellTextSelection: true,
overlayLoadingTemplate: t('Loading...'),
overlayNoRowsTemplate: t('No data'),
};
const wrapperClasses = classNames('vega-ag-grid', {

View File

@ -1,9 +1,8 @@
import compact from 'lodash/compact';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import { useFillsList } from './use-fills-list';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
@ -22,80 +21,30 @@ export const FillsManager = ({
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
const {
data,
error,
loading,
addNewRows,
getRows,
reload,
makeBottomPlaceholders,
} = useFillsList({
const { data, error } = useFillsList({
partyId,
marketId,
gridRef,
scrolledToTop,
});
const checkBottomPlaceholder = useCallback(() => {
const rowCont = gridRef.current?.api?.getModel().getRowCount() ?? 0;
const lastRowIndex = gridRef.current?.api?.getLastDisplayedRow();
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
const lastrow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
lastrow?.setRowHeight(50);
makeBottomPlaceholders(lastrow?.data);
gridRef.current?.api.onRowHeightChanged();
gridRef.current?.api.refreshInfiniteCache();
}
}, [makeBottomPlaceholders]);
const bottomPlaceholderProps = useBottomPlaceholder({
gridRef,
});
const onBodyScrollEnd = useCallback(
(event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
checkBottomPlaceholder();
},
[addNewRows, checkBottomPlaceholder]
);
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
}, []);
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
useBottomPlaceholder({
gridRef,
});
const fills = compact(data).map((e) => e.node);
return (
<div className="h-full relative">
<FillsTable
ref={gridRef}
rowData={fills}
partyId={partyId}
rowModelType="infinite"
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
onMarketClick={onMarketClick}
suppressLoadingOverlay
suppressNoRowsOverlay
isFullWidthRow={isFullWidthRow}
fullWidthCellRenderer={fullWidthCellRenderer}
rowClassRules={rowClassRules}
getRowHeight={getRowHeight}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No fills')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
);
};

View File

@ -1,11 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import type * as Schema from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { subDays, formatRFC3339 } from 'date-fns';
import type { AggregatedLedgerEntriesNode } from './ledger-entries-data-provider';
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
import { LedgerTable } from './ledger-table';
import type * as Types from '@vegaprotocol/types';
@ -27,9 +25,8 @@ const defaultFilter = {
export const LedgerManager = ({ partyId }: { partyId: string }) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter>(defaultFilter);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = useLedgerEntriesDataProvider({
const { data, error } = useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
@ -39,16 +36,9 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
}, []);
const extractNodesDecorator = useCallback(
(data: AggregatedLedgerEntriesNode[] | null, loading: boolean) =>
data && !loading ? data.map((item) => item.node) : null,
[]
);
const extractedData = extractNodesDecorator(data, loading);
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [extractedData]);
// allow passing undefined to grid so that loading state is shown
const extractedData = data?.map((item) => item.node);
return (
<div className="h-full relative">
@ -56,20 +46,11 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
ref={gridRef}
rowData={extractedData}
onFilterChanged={onFilterChanged}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
/>
{extractedData && (
<LedgerExportLink entries={extractedData} partyId={partyId} />
)}
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No entries')}
noDataCondition={() => !dataCount}
reload={reload}
/>
</div>
</div>
);
};

View File

@ -1,9 +1,8 @@
import type { MouseEvent } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { CellClickedEvent } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
@ -16,25 +15,10 @@ interface MarketsContainerProps {
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<MarketMaybeWithData[] | null>(null);
const [dataCount, setDataCount] = useState(1);
const handleDataCount = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
const update = useCallback(
({ data }: { data: MarketMaybeWithData[] | null }) => {
data && gridRef.current?.api?.setRowData(data);
dataRef.current = data;
handleDataCount();
return true;
},
[handleDataCount]
);
const { error, loading, reload } = useDataProvider({
const { data, error, reload } = useDataProvider({
dataProvider,
variables: undefined,
update,
});
useEffect(() => {
@ -46,17 +30,11 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
};
}, [reload]);
const handleOnGridReady = useCallback(() => {
dataRef?.current && update({ data: dataRef.current });
handleDataCount();
}, [handleDataCount, update]);
return (
<div className="h-full relative">
<MarketListTable
ref={gridRef}
suppressLoadingOverlay
suppressNoRowsOverlay
rowData={data}
onCellClicked={(cellEvent: CellClickedEvent) => {
const { data, column, event } = cellEvent;
// prevent navigating to the market page if any of the below cells are clicked
@ -79,19 +57,8 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
);
}}
onMarketClick={onSelect}
onFilterChanged={handleDataCount}
onGridReady={handleOnGridReady}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={dataRef?.current || []}
noDataMessage={t('No markets')}
noDataCondition={() => !dataCount}
reload={reload}
/>
</div>
</div>
);
};

View File

@ -24,41 +24,6 @@ const generateJsx = () => {
};
describe('OrderListManager', () => {
it('should render a loading state while awaiting orders', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: true,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: 0,
});
await act(async () => {
render(generateJsx());
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should render an error state', async () => {
const errorMsg = 'Oops! An Error';
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: null,
loading: false,
error: new Error(errorMsg),
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
await act(async () => {
render(generateJsx());
});
expect(
screen.getByText(`Something went wrong: ${errorMsg}`)
).toBeInTheDocument();
});
it('should render the order list if orders provided', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [{ id: '1' } as OrderFieldsFragment],
@ -74,20 +39,4 @@ describe('OrderListManager', () => {
});
expect(await screen.findByText('OrderList')).toBeInTheDocument();
});
it('should show no orders message', async () => {
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [],
loading: false,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
await act(async () => {
render(generateJsx());
});
expect(screen.getByText('No orders')).toBeInTheDocument();
});
});

View File

@ -1,6 +1,5 @@
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
@ -72,7 +71,6 @@ export const OrderListManager = ({
storeKey,
}: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [hasData, setHasData] = useState(false);
const [editOrder, setEditOrder] = useState<Order | null>(null);
const create = useVegaTransactionStore((state) => state.create);
const hasAmendableOrder = useHasAmendableOrder(marketId);
@ -81,7 +79,7 @@ export const OrderListManager = ({
? { partyId, filter: { liveOnly: true } }
: { partyId };
const { data, error, loading, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: ordersWithMarketProvider,
variables,
update: ({ data }) => {
@ -129,22 +127,11 @@ export const OrderListManager = ({
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
const rowCount = gridRef.current?.api?.getModel().getRowCount();
setHasData((rowCount ?? 0) > 0);
bottomPlaceholderOnFilterChanged?.();
},
[bottomPlaceholderOnFilterChanged]
);
const onRowDataChanged = useCallback(() => {
const rowCount = gridRef.current?.api?.getModel().getRowCount();
setHasData((rowCount ?? 0) > 0);
}, []);
useEffect(() => {
setHasData(Boolean(data?.length));
}, [data]);
const cancelAll = useCallback(() => {
create({
orderCancellation: {},
@ -164,24 +151,12 @@ export const OrderListManager = ({
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
onFilterChanged={onFilterChanged}
onRowDataChanged={onRowDataChanged}
isReadOnly={isReadOnly}
storeKey={storeKey}
suppressLoadingOverlay
suppressNoRowsOverlay
suppressAutoSize
overlayNoRowsTemplate={error ? error.message : t('No orders')}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No orders')}
noDataCondition={(data) => !hasData}
reload={reload}
/>
</div>
</div>
{!isReadOnly && hasAmendableOrder && (
<CancelAllOrdersButton onClick={cancelAll} />

View File

@ -44,15 +44,6 @@ const generateJsx = (
};
describe('OrderListTable', () => {
it('should show no orders message', async () => {
await act(async () => {
render(generateJsx({ rowData: [] }));
});
expect(() => screen.getByText('No orders')).toThrow(
'Unable to find an element'
);
});
it('should render correct columns', async () => {
await act(async () => {
render(generateJsx({ rowData: [marketOrder, limitOrder] }));

View File

@ -210,10 +210,13 @@ const positionDataProvider = makeDerivedDataProvider<
partyIds: variables.partyIds,
}),
],
(data, variables) =>
(data[0] as PositionFieldsFragment[] | null)?.find(
(p) => p.market.id === variables?.marketId
) || null
(data, variables) => {
return (
(data[0] as PositionFieldsFragment[] | null)?.find(
(p) => p.market.id === variables?.marketId
) || null
);
}
);
export const openVolumeDataProvider = makeDerivedDataProvider<

View File

@ -1,5 +1,4 @@
import { useCallback, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRef } from 'react';
import { usePositionsData } from './use-positions-data';
import { PositionsTable } from './positions-table';
import type { AgGridReact } from 'ag-grid-react';
@ -26,8 +25,7 @@ export const PositionsManager = ({
}: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet();
const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading, reload } = usePositionsData(partyIds, gridRef);
const [dataCount, setDataCount] = useState(data?.length ?? 0);
const { data, error } = usePositionsData(partyIds, gridRef);
const create = useVegaTransactionStore((store) => store.create);
const onClose = ({
marketId,
@ -63,9 +61,6 @@ export const PositionsManager = ({
gridRef,
disabled: noBottomPlaceholder,
});
const updateRowCount = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return (
<div className="h-full relative">
@ -76,25 +71,12 @@ export const PositionsManager = ({
ref={gridRef}
onMarketClick={onMarketClick}
onClose={onClose}
suppressLoadingOverlay
suppressNoRowsOverlay
isReadOnly={isReadOnly}
onFilterChanged={updateRowCount}
onRowDataUpdated={updateRowCount}
{...bottomPlaceholderProps}
storeKey={storeKey}
multipleKeys={partyIds.length > 1}
overlayNoRowsTemplate={error ? error.message : t('No positions')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No positions')}
noDataCondition={(data) => !dataCount}
reload={reload}
/>
</div>
</div>
);
};

View File

@ -110,17 +110,29 @@ describe('ProposalsList', () => {
});
it('empty response should causes no data message display', async () => {
const mock: MockedResponse<ProposalsListQuery> = {
request: {
query: ProposalsListDocument,
variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
},
result: {
data: {
proposalsConnection: {
__typename: 'ProposalsConnection',
edges: [],
},
},
},
};
await act(() => {
render(
<MockedProvider>
<MockedProvider mocks={[mock]}>
<ProposalsList />
</MockedProvider>
);
});
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => {
expect(container).toBeInTheDocument();
});
expect(screen.getByText('No markets')).toBeInTheDocument();
expect(await screen.findByText('No markets')).toBeInTheDocument();
});
});

View File

@ -1,5 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRef } from 'react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types';
@ -20,8 +19,7 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
export const ProposalsList = () => {
const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, loading, error, refetch } = useProposalsListQuery({
const { data, error } = useProposalsListQuery({
variables: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
@ -31,12 +29,6 @@ export const ProposalsList = () => {
removePaginationWrapper(data?.proposalsConnection?.edges)
);
const { columnDefs, defaultColDef } = useColumnDefs();
const handleDataCount = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
useEffect(() => {
handleDataCount();
}, [filteredData, handleDataCount]);
return (
<div className="relative h-full">
@ -46,24 +38,11 @@ export const ProposalsList = () => {
columnDefs={columnDefs}
rowData={filteredData}
defaultColDef={defaultColDef}
suppressLoadingOverlay
suppressNoRowsOverlay
onFilterChanged={handleDataCount}
storeKey="proposedMarkets"
getRowId={({ data }) => data.id}
style={{ width: '100%', height: '100%' }}
onGridReady={handleDataCount}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={filteredData}
noDataMessage={t('No markets')}
noDataCondition={() => !dataCount}
reload={refetch}
/>
</div>
</div>
);
};

View File

@ -1,13 +1,11 @@
import { makeInfiniteScrollGetRows } from '@vegaprotocol/data-provider';
import compact from 'lodash/compact';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef } from 'react';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import { useRef } from 'react';
import { tradesWithMarketProvider } from './trades-data-provider';
import { TradesTable } from './trades-table';
import type { Trade, TradeEdge } from './trades-data-provider';
import { useOrderStore } from '@vegaprotocol/orders';
import { t } from '@vegaprotocol/i18n';
interface TradesContainerProps {
marketId: string;
@ -15,104 +13,24 @@ interface TradesContainerProps {
export const TradesContainer = ({ marketId }: TradesContainerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<(TradeEdge | null)[] | null>(null);
const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0);
const scrolledToTop = useRef(true);
const updateOrder = useOrderStore((store) => store.update);
const addNewRows = useCallback(() => {
if (newRows.current === 0) {
return;
}
if (totalCountRef.current !== undefined) {
totalCountRef.current += newRows.current;
}
newRows.current = 0;
gridRef.current?.api?.refreshInfiniteCache();
}, []);
const update = useCallback(
({
data,
delta,
}: {
data: (TradeEdge | null)[] | null;
delta?: Trade[];
}) => {
if (dataRef.current?.length) {
if (!scrolledToTop.current) {
const createdAt = dataRef.current?.[0]?.node.createdAt;
if (createdAt) {
newRows.current += (delta || []).filter(
(trade) => trade.createdAt > createdAt
).length;
}
}
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
return true;
}
dataRef.current = data;
return false;
},
[]
);
const insert = useCallback(
({
data,
totalCount,
}: {
data: (TradeEdge | null)[] | null;
totalCount?: number;
}) => {
dataRef.current = data;
totalCountRef.current = totalCount;
return true;
},
[]
);
const { data, error, loading, load, totalCount, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: tradesWithMarketProvider,
update,
insert,
variables: { marketId },
});
totalCountRef.current = totalCount;
const getRows = makeInfiniteScrollGetRows<TradeEdge>(
dataRef,
totalCountRef,
load,
newRows
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
};
const onBodyScroll = (event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
};
const trades = compact(data).map((d) => d.node);
return (
<AsyncRenderer loading={loading} error={error} data={data} reload={reload}>
<TradesTable
ref={gridRef}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
rowData={data?.length ? undefined : []}
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
onClick={(price?: string) => {
if (price) {
updateOrder(marketId, { price });
}
}}
/>
</AsyncRenderer>
<TradesTable
ref={gridRef}
rowData={trades}
onClick={(price?: string) => {
if (price) {
updateOrder(marketId, { price });
}
}}
overlayNoRowsTemplate={error ? error.message : t('No trades')}
/>
);
};

View File

@ -2,8 +2,6 @@ import {
makeDataProvider,
makeDerivedDataProvider,
defaultAppend as append,
paginatedCombineDelta as combineDelta,
paginatedCombineInsertionData as combineInsertionData,
} from '@vegaprotocol/data-provider';
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
import type { Market } from '@vegaprotocol/markets';
@ -18,7 +16,7 @@ import { TradesDocument, TradesUpdateDocument } from './__generated__/Trades';
import orderBy from 'lodash/orderBy';
import produce from 'immer';
export const MAX_TRADES = 50;
export const MAX_TRADES = 500;
const getData = (
responseData: TradesQuery | null
@ -34,32 +32,25 @@ const update = (
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getDelta>
) => {
if (!data) return data;
return produce(data, (draft) => {
// for each incoming trade add it to the beginning and remove oldest trade
orderBy(delta, 'createdAt', 'desc').forEach((node) => {
if (!draft) {
return;
}
const index = draft.findIndex((edge) => edge?.node.id === node.id);
if (index !== -1) {
if (draft?.[index]?.node) {
Object.assign(draft[index]?.node as TradeFieldsFragment, node);
}
} else {
const firstNode = draft[0]?.node;
if (firstNode && node.createdAt >= firstNode.createdAt) {
const { marketId, ...nodeData } = node;
draft.unshift({
node: {
...nodeData,
__typename: 'Trade',
market: {
__typename: 'Market',
id: marketId,
},
},
cursor: '',
});
}
const { marketId, ...nodeData } = node;
draft.unshift({
node: {
...nodeData,
__typename: 'Trade',
market: {
__typename: 'Market',
id: marketId,
},
},
cursor: '',
});
if (draft.length > MAX_TRADES) {
draft.pop();
}
});
});
@ -86,7 +77,7 @@ export const tradesProvider = makeDataProvider<
pagination: {
getPageInfo,
append,
first: 100,
first: MAX_TRADES,
},
});
@ -117,7 +108,5 @@ export const tradesWithMarketProvider = makeDerivedDataProvider<
node,
};
});
},
combineDelta<Trade, ReturnType<typeof getDelta>['0']>,
combineInsertionData<Trade>
}
);

View File

@ -51,7 +51,6 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No trades')}
getRowId={({ data }) => data.id}
ref={ref}
defaultColDef={{