feat(trading): divide order tab into open, closed, rejected and all (#3541)
This commit is contained in:
parent
768b3b29f0
commit
c37f9ebe66
@ -9,6 +9,21 @@ describe('market bottom panel', { tags: '@smoke' }, () => {
|
||||
|
||||
it('on xxl screen should be splitted out into two tables', () => {
|
||||
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-open-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-closed-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-rejected-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-accounts').should(
|
||||
@ -19,7 +34,22 @@ describe('market bottom panel', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.viewport(1801, 1000);
|
||||
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-open-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-closed-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-rejected-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-accounts').should(
|
||||
'have.attr',
|
||||
|
@ -29,7 +29,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
@ -87,7 +87,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Partially Filled').click();
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
||||
.eq(1)
|
||||
@ -116,7 +116,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Reset').click();
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get(`.ag-center-cols-container [col-id='${orderSymbol}']`)
|
||||
@ -139,14 +139,15 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
|
||||
describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
let orderId = '0';
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
@ -154,8 +155,8 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
.click();
|
||||
cy.get('.ag-filter-apply-panel-button').click();
|
||||
});
|
||||
orderId = (parseInt(orderId, 10) + 1).toString();
|
||||
});
|
||||
const orderId = '1234567890';
|
||||
// 7002-SORD-053
|
||||
// 7002-SORD-040
|
||||
// 7003-MORD-001
|
||||
@ -299,7 +300,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '200.00');
|
||||
.should('have.text', '-');
|
||||
});
|
||||
|
||||
it('must see the time in force applied to the order', () => {
|
||||
@ -370,7 +371,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
import { MarketInfoAccordionContainer } from '@vegaprotocol/market-info';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { OrderListContainer, Filter } from '@vegaprotocol/orders';
|
||||
import type { OrderListContainerProps } from '@vegaprotocol/orders';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
@ -58,17 +59,59 @@ const requiresMarket = (View: MarketDependantView) => {
|
||||
};
|
||||
|
||||
const TradingViews = {
|
||||
Candles: requiresMarket(CandlesChartContainer),
|
||||
Depth: requiresMarket(DepthChartContainer),
|
||||
Liquidity: requiresMarket(LiquidityContainer),
|
||||
Ticket: requiresMarket(DealTicketContainer),
|
||||
Info: requiresMarket(MarketInfoAccordionContainer),
|
||||
Orderbook: requiresMarket(OrderbookContainer),
|
||||
Trades: requiresMarket(TradesContainer),
|
||||
Positions: PositionsContainer,
|
||||
Orders: OrderListContainer,
|
||||
Collateral: AccountsContainer,
|
||||
Fills: FillsContainer,
|
||||
candles: {
|
||||
label: 'Candles',
|
||||
component: requiresMarket(CandlesChartContainer),
|
||||
},
|
||||
depth: {
|
||||
label: 'Depth',
|
||||
component: requiresMarket(DepthChartContainer),
|
||||
},
|
||||
liquidity: {
|
||||
label: 'Liquidity',
|
||||
component: requiresMarket(LiquidityContainer),
|
||||
},
|
||||
ticket: {
|
||||
label: 'Ticket',
|
||||
component: requiresMarket(DealTicketContainer),
|
||||
},
|
||||
info: {
|
||||
label: 'Info',
|
||||
component: requiresMarket(MarketInfoAccordionContainer),
|
||||
},
|
||||
orderbook: {
|
||||
label: 'Orderbook',
|
||||
component: requiresMarket(OrderbookContainer),
|
||||
},
|
||||
trades: {
|
||||
label: 'Trades',
|
||||
component: requiresMarket(TradesContainer),
|
||||
},
|
||||
positions: { label: 'Positions', component: PositionsContainer },
|
||||
activeOrders: {
|
||||
label: 'Active',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Open} />
|
||||
),
|
||||
},
|
||||
closedOrders: {
|
||||
label: 'Closed',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Closed} />
|
||||
),
|
||||
},
|
||||
rejectedOrders: {
|
||||
label: 'Rejected',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Rejected} />
|
||||
),
|
||||
},
|
||||
orders: {
|
||||
label: 'All',
|
||||
component: OrderListContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
};
|
||||
|
||||
type TradingView = keyof typeof TradingViews;
|
||||
@ -104,9 +147,42 @@ const MarketBottomPanel = memo(
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-bottom-left">
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Orders
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
@ -116,7 +192,7 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Fills
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
@ -134,7 +210,7 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom-right">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
/>
|
||||
@ -142,7 +218,7 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Collateral
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
noBottomPlaceholder
|
||||
hideButtons
|
||||
@ -158,12 +234,45 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions onMarketClick={onMarketClick} />
|
||||
<TradingViews.positions.component onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Orders
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
@ -173,7 +282,7 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Fills
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
@ -181,7 +290,10 @@ const MarketBottomPanel = memo(
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
hideButtons
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@ -221,13 +333,13 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-left">
|
||||
<Tab id="chart" name={t('Chart')}>
|
||||
<TradingViews.Candles marketId={marketId} />
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
<TradingViews.Depth marketId={marketId} />
|
||||
<TradingViews.depth.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="liquidity" name={t('Liquidity')}>
|
||||
<TradingViews.Liquidity marketId={marketId} />
|
||||
<TradingViews.liquidity.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@ -240,13 +352,13 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-center">
|
||||
<Tab id="ticket" name={t('Ticket')}>
|
||||
<TradingViews.Ticket
|
||||
<TradingViews.ticket.component
|
||||
marketId={marketId}
|
||||
onClickCollateral={() => navigate('/portfolio')}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="info" name={t('Info')}>
|
||||
<TradingViews.Info marketId={marketId} />
|
||||
<TradingViews.info.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@ -259,10 +371,10 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-right">
|
||||
<Tab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook marketId={marketId} />
|
||||
<TradingViews.orderbook.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="trades" name={t('Trades')}>
|
||||
<TradingViews.Trades marketId={marketId} />
|
||||
<TradingViews.trades.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@ -330,7 +442,7 @@ export const TradePanels = ({
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
|
||||
const [view, setView] = useState<TradingView>('Candles');
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
const renderView = () => {
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
@ -339,7 +451,7 @@ export const TradePanels = ({
|
||||
onOrderTypeClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}>(TradingViews[view]);
|
||||
}>(TradingViews[view].component);
|
||||
|
||||
if (!Component) {
|
||||
throw new Error(`No component for view: ${view}`);
|
||||
@ -388,7 +500,7 @@ export const TradePanels = ({
|
||||
className={className}
|
||||
key={key}
|
||||
>
|
||||
{key}
|
||||
{TradingViews[key as keyof typeof TradingViews].label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
@ -35,7 +35,7 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { ENV } from '../lib/config';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { activeOrdersProvider, allOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
@ -150,6 +150,11 @@ const PartyData = () => {
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
useDataProvider({
|
||||
dataProvider: allOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
|
@ -37,9 +37,9 @@ export const AccountManager = ({
|
||||
variables,
|
||||
});
|
||||
const setId = useCallback(
|
||||
(data: AccountFields) => ({
|
||||
(data: AccountFields, id: string) => ({
|
||||
...data,
|
||||
asset: { ...data.asset, id: `${data.asset.id}-1` },
|
||||
asset: { ...data.asset, id },
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
@ -126,14 +126,20 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
return currentPinnedAssetRow;
|
||||
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) =>
|
||||
const { getRowHeight } = props;
|
||||
|
||||
const getPinnedAssetRowHeight = useCallback(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAssetId &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
? 32
|
||||
: 24,
|
||||
[pinnedAssetId]
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
[pinnedAssetId, getRowHeight]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -155,7 +161,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
}}
|
||||
getRowHeight={getRowHeight}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedAssetRow ? [pinnedAssetRow] : undefined}
|
||||
>
|
||||
<AgGridColumn
|
||||
|
@ -9,10 +9,11 @@ import {
|
||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
export const SetFilter = forwardRef(
|
||||
(props: IFilterParams & { readonly?: boolean }, ref) => {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
const valueRef = useRef(value);
|
||||
|
||||
const { readonly } = props;
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
@ -62,7 +63,6 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
useEffect(() => {
|
||||
props.filterChangedCallback();
|
||||
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div className="ag-filter-body-wrapper">
|
||||
<fieldset className="ag-simple-filter-body-wrapper">
|
||||
@ -71,6 +71,7 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
<input
|
||||
type="checkbox"
|
||||
value={key}
|
||||
disabled={readonly}
|
||||
className="mr-1"
|
||||
checked={value.includes(key)}
|
||||
onChange={onChange}
|
||||
@ -79,15 +80,19 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
{!readonly && (
|
||||
<div className="ag-filter-apply-panel">
|
||||
<button
|
||||
type="button"
|
||||
disabled={readonly}
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue((valueRef.current = []))}
|
||||
>
|
||||
{t('Reset')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -62,7 +62,7 @@ export const FillsManager = ({
|
||||
scrolledToTop.current = event.top <= 0;
|
||||
}, []);
|
||||
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules } =
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
|
||||
useBottomPlaceholder<Trade>({
|
||||
gridRef,
|
||||
});
|
||||
@ -82,6 +82,7 @@ export const FillsManager = ({
|
||||
isFullWidthRow={isFullWidthRow}
|
||||
fullWidthCellRenderer={fullWidthCellRenderer}
|
||||
rowClassRules={rowClassRules}
|
||||
getRowHeight={getRowHeight}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
|
@ -7,15 +7,14 @@ describe('order data provider', () => {
|
||||
const data = [
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
},
|
||||
] as Edge<OrderFieldsFragment>[];
|
||||
@ -24,47 +23,51 @@ describe('order data provider', () => {
|
||||
// this one should be dropped because id don't exits and it's older than newest
|
||||
{
|
||||
id: '0',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
createdAt: new Date('2022-01-27').toISOString(),
|
||||
},
|
||||
// this one should be dropped because newer below
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-01').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-02').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
updatedAt: new Date('2022-02-04').toISOString(),
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
// this should be added
|
||||
{
|
||||
id: '4',
|
||||
createdAt: new Date('2022-02-04').toISOString(),
|
||||
},
|
||||
// this should be move to top
|
||||
{
|
||||
id: '2',
|
||||
updatedAt: new Date('2022-02-03').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
updatedAt: new Date('2022-02-04').toISOString(),
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
// this should be added
|
||||
{
|
||||
id: '5',
|
||||
createdAt: new Date('2022-02-05').toISOString(),
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
|
||||
const updatedData = update(data, delta, () => null, { partyId: '0x123' });
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
expect(updatedData && updatedData[3].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[3].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[5].id);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[4].updatedAt
|
||||
);
|
||||
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
|
||||
4
|
||||
5
|
||||
);
|
||||
});
|
||||
it('add only data matching date range filter', () => {
|
||||
@ -72,7 +75,6 @@ describe('order data provider', () => {
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
},
|
||||
@ -90,12 +92,6 @@ describe('order data provider', () => {
|
||||
id: '0',
|
||||
createdAt: new Date('2022-02-02').toISOString(),
|
||||
},
|
||||
// this one should be removed because it does not match date range
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-02').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
// this one should be updated
|
||||
{
|
||||
id: '2',
|
||||
@ -118,16 +114,13 @@ describe('order data provider', () => {
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[1].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[0].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
delta[3].updatedAt
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[1].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[1].updatedAt
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -5,8 +5,6 @@ import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
paginatedCombineDelta as combineDelta,
|
||||
paginatedCombineInsertionData as combineInsertionData,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { marketsProvider } from '@vegaprotocol/market-list';
|
||||
@ -28,6 +26,11 @@ export type Order = Omit<OrderFieldsFragment, 'market'> & {
|
||||
};
|
||||
export type OrderEdge = Edge<Order>;
|
||||
|
||||
const liveOnlyOrderStatuses = [
|
||||
OrderStatus.STATUS_ACTIVE,
|
||||
OrderStatus.STATUS_PARKED,
|
||||
];
|
||||
|
||||
const orderMatchFilters = (
|
||||
order: OrderUpdateFieldsFragment,
|
||||
variables: OrdersQueryVariables
|
||||
@ -41,6 +44,12 @@ const orderMatchFilters = (
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.liveOnly &&
|
||||
!(order.status && liveOnlyOrderStatuses.includes(order.status))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.types &&
|
||||
!(order.type && variables.filter.types.includes(order.type))
|
||||
@ -58,19 +67,13 @@ const orderMatchFilters = (
|
||||
}
|
||||
if (
|
||||
variables?.filter?.dateRange?.start &&
|
||||
!(
|
||||
(order.updatedAt || order.createdAt) &&
|
||||
variables.filter.dateRange.start < (order.updatedAt || order.createdAt)
|
||||
)
|
||||
!(order.createdAt && variables.filter.dateRange.start < order.createdAt)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.dateRange?.end &&
|
||||
!(
|
||||
(order.updatedAt || order.createdAt) &&
|
||||
variables.filter.dateRange.end > (order.updatedAt || order.createdAt)
|
||||
)
|
||||
!(order.createdAt && variables.filter.dateRange.end > order.createdAt)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -120,28 +123,25 @@ export const update = (
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
return produce(data, (draft) => {
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
const incoming = uniqBy(
|
||||
const incoming = orderBy(
|
||||
uniqBy(
|
||||
orderBy(delta, (order) => order.updatedAt || order.createdAt, 'desc'),
|
||||
'id'
|
||||
),
|
||||
'createdAt'
|
||||
);
|
||||
|
||||
return produce(data, (draft) => {
|
||||
// Add or update incoming orders
|
||||
incoming.reverse().forEach((node) => {
|
||||
incoming.forEach((node) => {
|
||||
const index = draft.findIndex((edge) => edge.node.id === node.id);
|
||||
const newer =
|
||||
draft.length === 0 ||
|
||||
(node.updatedAt || node.createdAt) >=
|
||||
(draft[0].node.updatedAt || draft[0].node.createdAt);
|
||||
draft.length === 0 || node.createdAt >= draft[0].node.createdAt;
|
||||
const doesFilterPass = !variables || orderMatchFilters(node, variables);
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
Object.assign(draft[index].node, node);
|
||||
if (newer) {
|
||||
draft.unshift(...draft.splice(index, 1));
|
||||
}
|
||||
} else {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
@ -194,6 +194,34 @@ const ordersProvider = makeDataProvider<
|
||||
additionalContext: { isEnlargedTimeout: true },
|
||||
});
|
||||
|
||||
const allOrderMaxCount = 50000;
|
||||
|
||||
export const allOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
ordersProvider(callback, client, { partyId: variables.partyId }),
|
||||
],
|
||||
(partsData, variables, prevData, parts, subscriptions) => {
|
||||
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||
// load next pages until allOrderMaxCount reached
|
||||
if (
|
||||
!parts[0].isUpdate &&
|
||||
subscriptions &&
|
||||
subscriptions[0].load &&
|
||||
orders?.length < allOrderMaxCount
|
||||
) {
|
||||
subscriptions[0].load();
|
||||
}
|
||||
return variables.marketId
|
||||
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||
: orders;
|
||||
}
|
||||
);
|
||||
|
||||
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
@ -204,11 +232,12 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
ordersProvider(callback, client, {
|
||||
partyId: variables.partyId,
|
||||
filter: {
|
||||
status: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
liveOnly: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
(partsData, variables, prevData, parts, subscriptions) => {
|
||||
// load all pages
|
||||
if (!parts[0].isUpdate && subscriptions && subscriptions[0].load) {
|
||||
subscriptions[0].load();
|
||||
}
|
||||
@ -220,7 +249,7 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
);
|
||||
|
||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
(OrderEdge | null)[],
|
||||
(Order | null)[],
|
||||
Order[],
|
||||
OrdersQueryVariables
|
||||
>(
|
||||
@ -228,18 +257,13 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
ordersProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): OrderEdge[] =>
|
||||
(partsData): Order[] =>
|
||||
((partsData[0] as ReturnType<typeof getData>) || []).map((edge) => ({
|
||||
cursor: edge.cursor,
|
||||
node: {
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
},
|
||||
})),
|
||||
combineDelta<Order, ReturnType<typeof getDelta>['0']>,
|
||||
combineInsertionData<Order>
|
||||
}))
|
||||
);
|
||||
|
||||
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||
|
@ -2,18 +2,23 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { OrderListManager } from './order-list-manager';
|
||||
import type { Filter } from './order-list-manager';
|
||||
|
||||
export interface OrderListContainerProps {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
export const OrderListContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
enforceBottomPlaceholder,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
}) => {
|
||||
filter,
|
||||
}: OrderListContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
@ -24,6 +29,7 @@ export const OrderListContainer = ({
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
isReadOnly={isReadOnly}
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './order-list-manager';
|
||||
export * from './use-order-list-data';
|
||||
|
@ -1,25 +1,41 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { FilterChangedEvent, SortChangedEvent } from 'ag-grid-community';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { GridReadyEvent } from 'ag-grid-community';
|
||||
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
|
||||
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
import { useOrderListData } from './use-order-list-data';
|
||||
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||
import type { Filter, Sort } from './use-order-list-data';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import {
|
||||
normalizeOrderAmendment,
|
||||
useVegaTransactionStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order, OrderEdge } from '../order-data-provider';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export enum Filter {
|
||||
'Open',
|
||||
'Closed',
|
||||
'Rejected',
|
||||
}
|
||||
|
||||
const FilterStatusValue = {
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
[Filter.Closed]: [
|
||||
OrderStatus.STATUS_CANCELLED,
|
||||
OrderStatus.STATUS_EXPIRED,
|
||||
OrderStatus.STATUS_FILLED,
|
||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
OrderStatus.STATUS_STOPPED,
|
||||
],
|
||||
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
|
||||
};
|
||||
|
||||
export interface OrderListManagerProps {
|
||||
partyId: string;
|
||||
@ -28,6 +44,7 @@ export interface OrderListManagerProps {
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
@ -43,12 +60,6 @@ const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const initialFilter: Filter = {
|
||||
status: {
|
||||
value: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
},
|
||||
};
|
||||
|
||||
export const OrderListManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
@ -56,27 +67,22 @@ export const OrderListManager = ({
|
||||
onOrderTypeClick,
|
||||
isReadOnly,
|
||||
enforceBottomPlaceholder,
|
||||
filter,
|
||||
}: OrderListManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [dataCount, setDataCount] = useState(0);
|
||||
const scrolledToTop = useRef(false);
|
||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||
const [filter, setFilter] = useState<Filter | undefined>(initialFilter);
|
||||
const filterRef = useRef(initialFilter);
|
||||
const [hasData, setHasData] = useState(false);
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
|
||||
const { data, error, loading, reload } = useOrderListData({
|
||||
partyId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
variables:
|
||||
filter === Filter.Open
|
||||
? { partyId, filter: { liveOnly: true } }
|
||||
: { partyId },
|
||||
});
|
||||
|
||||
const {
|
||||
onSortChanged: bottomPlaceholderOnSortChanged,
|
||||
onFilterChanged: bottomPlaceholderOnFilterChanged,
|
||||
...bottomPlaceholderProps
|
||||
} = useBottomPlaceholder<Order>({
|
||||
@ -84,42 +90,6 @@ export const OrderListManager = ({
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = event.api.getFilterModel();
|
||||
if (isEqual(updatedFilter, filterRef.current)) {
|
||||
return;
|
||||
}
|
||||
filterRef.current = updatedFilter;
|
||||
if (Object.keys(updatedFilter).length) {
|
||||
setFilter(updatedFilter);
|
||||
} else {
|
||||
setFilter(undefined);
|
||||
}
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
bottomPlaceholderOnFilterChanged?.();
|
||||
},
|
||||
[setFilter, bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
const onSortChange = useCallback(
|
||||
(event: SortChangedEvent) => {
|
||||
const sort = event.columnApi
|
||||
.getColumnState()
|
||||
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
||||
.reduce((acc, col) => {
|
||||
if (col.sort) {
|
||||
const { colId, sort } = col;
|
||||
acc.push({ colId, sort });
|
||||
}
|
||||
return acc;
|
||||
}, [] as { colId: string; sort: string }[]);
|
||||
setSort(sort.length > 0 ? sort : undefined);
|
||||
bottomPlaceholderOnSortChanged?.();
|
||||
},
|
||||
[setSort, bottomPlaceholderOnSortChanged]
|
||||
);
|
||||
|
||||
const cancel = useCallback(
|
||||
(order: Order) => {
|
||||
if (!order.market) return;
|
||||
@ -133,12 +103,30 @@ export const OrderListManager = ({
|
||||
[create]
|
||||
);
|
||||
|
||||
const onGridReady = useCallback(({ api }: GridReadyEvent) => {
|
||||
api.setFilterModel(initialFilter);
|
||||
}, []);
|
||||
const onGridReady = useCallback(
|
||||
({ api }: GridReadyEvent) => {
|
||||
if (filter !== undefined) {
|
||||
api.setFilterModel({
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[filter]
|
||||
);
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const rowCount = gridRef.current?.api?.getModel().getRowCount();
|
||||
setHasData((rowCount ?? 0) > 0);
|
||||
bottomPlaceholderOnFilterChanged?.();
|
||||
},
|
||||
[bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
setHasData((gridRef.current?.api?.getModel().getRowCount() ?? 0) > 0);
|
||||
}, [data]);
|
||||
|
||||
const cancelAll = useCallback(() => {
|
||||
@ -148,26 +136,20 @@ export const OrderListManager = ({
|
||||
},
|
||||
});
|
||||
}, [create, marketId]);
|
||||
const extractedData =
|
||||
data && !loading
|
||||
? data
|
||||
.filter((item) => item !== null)
|
||||
.map((item) => (item as OrderEdge).node)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<OrderListTable
|
||||
rowData={extractedData}
|
||||
rowData={data as Order[]}
|
||||
ref={gridRef}
|
||||
readonlyStatusFilter={filter !== undefined}
|
||||
onGridReady={onGridReady}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onSortChanged={onSortChange}
|
||||
cancel={cancel}
|
||||
setEditOrder={setEditOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={onFilterChanged}
|
||||
isReadOnly={isReadOnly}
|
||||
blockLoadDebounceMillis={100}
|
||||
suppressLoadingOverlay
|
||||
@ -180,7 +162,7 @@ export const OrderListManager = ({
|
||||
error={error}
|
||||
data={data}
|
||||
noDataMessage={t('No orders')}
|
||||
noDataCondition={(data) => !dataCount}
|
||||
noDataCondition={(data) => !hasData}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,166 +0,0 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useOrderListData } from './use-order-list-data';
|
||||
import type { Edge } from '@vegaprotocol/utils';
|
||||
import type { OrderFieldsFragment } from '../order-data-provider/__generated__/Orders';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
|
||||
const loadMock = jest.fn();
|
||||
|
||||
let mockData: Edge<OrderFieldsFragment>[] | null = null;
|
||||
let mockDataProviderData = {
|
||||
data: mockData as (Edge<OrderFieldsFragment> | null)[] | null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
load: loadMock,
|
||||
totalCount: undefined,
|
||||
};
|
||||
|
||||
let updateMock: jest.Mock;
|
||||
const mockDataProvider = jest.fn((args) => {
|
||||
updateMock = args.update;
|
||||
return mockDataProviderData;
|
||||
});
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useDataProvider: jest.fn((args) => mockDataProvider(args)),
|
||||
}));
|
||||
|
||||
describe('useOrderListData Hook', () => {
|
||||
const mockRefreshAgGridApi = jest.fn();
|
||||
const partyId = 'partyId';
|
||||
const gridRef = {
|
||||
current: {
|
||||
api: {
|
||||
refreshInfiniteCache: mockRefreshAgGridApi,
|
||||
getModel: () => ({ getType: () => 'infinite' }),
|
||||
},
|
||||
} as unknown as AgGridReact,
|
||||
};
|
||||
const scrolledToTop = {
|
||||
current: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return proper dataProvider results', () => {
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('return proper mocked results', () => {
|
||||
mockData = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_1',
|
||||
createdAt: 1,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_2',
|
||||
createdAt: 2,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: mockData,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
updateMock({ data: mockData, delta: [] });
|
||||
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('methods for pagination should work', async () => {
|
||||
const successCallback = jest.fn();
|
||||
mockData = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_1',
|
||||
createdAt: 1,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_2',
|
||||
createdAt: 2,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
Object.assign(mockDataProviderData, {
|
||||
data: mockData,
|
||||
loading: false,
|
||||
});
|
||||
const mockDelta = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_3',
|
||||
createdAt: 3,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_4',
|
||||
createdAt: 4,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
const mockNextData = [...mockData, ...mockDelta];
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
|
||||
const getRowsParams = {
|
||||
successCallback,
|
||||
failCallback: jest.fn(),
|
||||
startRow: 2,
|
||||
endRow: 4,
|
||||
} as unknown as IGetRowsParams;
|
||||
|
||||
await waitFor(async () => {
|
||||
updateMock({ data: mockData });
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
const promise = result.current.getRows(getRowsParams);
|
||||
updateMock({ data: mockNextData, delta: mockDelta });
|
||||
await promise;
|
||||
});
|
||||
expect(loadMock).toHaveBeenCalled();
|
||||
expect(successCallback).toHaveBeenLastCalledWith(
|
||||
mockDelta.map((item) => item.node),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
@ -1,171 +0,0 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { makeInfiniteScrollGetRows } from '@vegaprotocol/utils';
|
||||
import { useDataProvider, updateGridData } from '@vegaprotocol/react-helpers';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import type {
|
||||
OrderEdge,
|
||||
Order,
|
||||
} from '../order-data-provider/order-data-provider';
|
||||
import type {
|
||||
OrdersQueryVariables,
|
||||
OrdersUpdateSubscriptionVariables,
|
||||
} from '../order-data-provider/__generated__/Orders';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
export interface Sort {
|
||||
colId: string;
|
||||
sort: string;
|
||||
}
|
||||
export interface Filter {
|
||||
updatedAt?: {
|
||||
value: Types.DateRange;
|
||||
};
|
||||
type?: {
|
||||
value: Types.OrderType[];
|
||||
};
|
||||
status?: {
|
||||
value: Types.OrderStatus[];
|
||||
};
|
||||
timeInForce?: {
|
||||
value: Types.OrderTimeInForce[];
|
||||
};
|
||||
}
|
||||
interface Props {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
filter?: Filter;
|
||||
sort?: Sort[];
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
scrolledToTop: RefObject<boolean>;
|
||||
}
|
||||
|
||||
export const useOrderListData = ({
|
||||
partyId,
|
||||
marketId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
}: Props) => {
|
||||
const dataRef = useRef<(OrderEdge | null)[] | null>(null);
|
||||
const totalCountRef = useRef<number | undefined>(undefined);
|
||||
const newRows = useRef(0);
|
||||
const placeholderAdded = useRef(-1);
|
||||
|
||||
const makeBottomPlaceholders = useCallback((order?: Order) => {
|
||||
if (!order) {
|
||||
if (placeholderAdded.current >= 0) {
|
||||
dataRef.current?.splice(placeholderAdded.current, 1);
|
||||
}
|
||||
placeholderAdded.current = -1;
|
||||
} else if (placeholderAdded.current === -1) {
|
||||
dataRef.current?.push({
|
||||
node: { ...order, id: `${order?.id}-1`, isLastPlaceholder: true },
|
||||
});
|
||||
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const variables = useMemo(() => {
|
||||
// define variable as const to get type safety, using generic with useMemo resulted in lost type safety
|
||||
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
|
||||
partyId,
|
||||
};
|
||||
if (
|
||||
filter?.updatedAt?.value ||
|
||||
filter?.status?.value.length ||
|
||||
filter?.timeInForce?.value.length ||
|
||||
filter?.type?.value.length
|
||||
) {
|
||||
allVars.filter = {};
|
||||
if (filter?.updatedAt?.value) {
|
||||
allVars.filter.dateRange = filter?.updatedAt?.value;
|
||||
}
|
||||
if (filter?.status?.value.length) {
|
||||
allVars.filter.status = filter?.status?.value;
|
||||
}
|
||||
if (filter?.timeInForce?.value.length) {
|
||||
allVars.filter.timeInForce = filter?.timeInForce?.value;
|
||||
}
|
||||
if (filter?.type?.value.length) {
|
||||
allVars.filter.types = filter?.type?.value;
|
||||
}
|
||||
}
|
||||
return allVars;
|
||||
}, [partyId, filter]);
|
||||
|
||||
const addNewRows = useCallback(() => {
|
||||
if (newRows.current === 0) {
|
||||
return;
|
||||
}
|
||||
if (totalCountRef.current !== undefined) {
|
||||
totalCountRef.current += newRows.current;
|
||||
}
|
||||
newRows.current = 0;
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
}, [gridRef]);
|
||||
|
||||
const update = useCallback(
|
||||
({
|
||||
data,
|
||||
delta,
|
||||
}: {
|
||||
data: (OrderEdge | null)[] | null;
|
||||
delta?: Order[];
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
if (dataRef.current?.length && delta?.length && !scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
newRows.current += (delta || []).filter(
|
||||
(trade) => trade.createdAt > createdAt
|
||||
).length;
|
||||
}
|
||||
}
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
|
||||
const insert = useCallback(
|
||||
({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: (OrderEdge | null)[] | null;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
totalCountRef.current = totalCount;
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
update,
|
||||
insert,
|
||||
variables,
|
||||
});
|
||||
totalCountRef.current = totalCount;
|
||||
|
||||
const getRows = useRef(
|
||||
makeInfiniteScrollGetRows<OrderEdge>(dataRef, totalCountRef, load, newRows)
|
||||
);
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
addNewRows,
|
||||
getRows: getRows.current,
|
||||
reload,
|
||||
makeBottomPlaceholders,
|
||||
};
|
||||
};
|
@ -33,13 +33,21 @@ export type OrderListTableProps = OrderListProps & {
|
||||
setEditOrder: (order: Order) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
readonlyStatusFilter?: boolean;
|
||||
isReadOnly: boolean;
|
||||
};
|
||||
|
||||
export const OrderListTable = memo(
|
||||
forwardRef<AgGridReact, OrderListTableProps>(
|
||||
(
|
||||
{ cancel, setEditOrder, onMarketClick, onOrderTypeClick, ...props },
|
||||
{
|
||||
cancel,
|
||||
setEditOrder,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
readonlyStatusFilter,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
@ -119,6 +127,7 @@ export const OrderListTable = memo(
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: Schema.OrderStatusMapping,
|
||||
readonly: readonlyStatusFilter,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
@ -229,6 +238,7 @@ export const OrderListTable = memo(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="createdAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
@ -243,7 +253,6 @@ export const OrderListTable = memo(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
|
@ -55,10 +55,10 @@ export const PositionsManager = ({
|
||||
},
|
||||
});
|
||||
|
||||
const setId = useCallback((data: Position) => {
|
||||
const setId = useCallback((data: Position, id: string) => {
|
||||
return {
|
||||
...data,
|
||||
marketId: `${data.marketId}-1`,
|
||||
marketId: id,
|
||||
};
|
||||
}, []);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
|
||||
|
@ -1,16 +1,17 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IsFullWidthRowParams } from 'ag-grid-community';
|
||||
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
|
||||
|
||||
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
|
||||
const ROW_ID = 'bottomPlaceholder';
|
||||
const fullWidthCellRenderer = () => null;
|
||||
const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
||||
params.rowNode.data?.isLastPlaceholder;
|
||||
|
||||
interface Props<T> {
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
setId?: (data: T) => T;
|
||||
setId?: (data: T, id: string) => T;
|
||||
disabled?: boolean;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
@ -20,45 +21,42 @@ export const useBottomPlaceholder = <T extends {}>({
|
||||
disabled,
|
||||
}: Props<T>) => {
|
||||
const onBodyScrollEnd = useCallback(() => {
|
||||
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
|
||||
const lastRowIndex = gridRef.current?.api.getLastDisplayedRow() ?? 0;
|
||||
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
|
||||
if (lastRow?.data && !lastRow?.data.isLastPlaceholder) {
|
||||
const newData = setId
|
||||
? setId({ ...lastRow.data, isLastPlaceholder: true })
|
||||
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
|
||||
if (rowCont) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
|
||||
if (lastRow && lastRow.data) {
|
||||
const placeholderRow = setId
|
||||
? setId({ ...lastRow.data, isLastPlaceholder: true }, ROW_ID)
|
||||
: {
|
||||
...lastRow.data,
|
||||
isLastPlaceholder: true,
|
||||
id: `${lastRow.data?.id || '-'}-1`,
|
||||
id: ROW_ID,
|
||||
};
|
||||
const add = [newData];
|
||||
const newIndex = lastRowIndex + 1;
|
||||
gridRef.current?.api.applyTransaction({
|
||||
add,
|
||||
addIndex: newIndex,
|
||||
});
|
||||
const newLastRow =
|
||||
gridRef.current?.api.getDisplayedRowAtIndex(newIndex);
|
||||
newLastRow?.setRowHeight(50);
|
||||
gridRef.current?.api.onRowHeightChanged();
|
||||
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
|
||||
? { update: [placeholderRow] }
|
||||
: { add: [placeholderRow] };
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}, [gridRef, setId]);
|
||||
|
||||
const onRowsChanged = useCallback(() => {
|
||||
const remove: T[] = [];
|
||||
gridRef.current?.api.forEachNodeAfterFilterAndSort((rowNode) => {
|
||||
if (rowNode.data.isLastPlaceholder) {
|
||||
remove.push(rowNode.data);
|
||||
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
|
||||
if (placeholderNode) {
|
||||
const transaction = {
|
||||
remove: [placeholderNode.data],
|
||||
};
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api.applyTransaction({
|
||||
remove,
|
||||
});
|
||||
onBodyScrollEnd();
|
||||
}, [gridRef, onBodyScrollEnd]);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) =>
|
||||
params.data?.isLastPlaceholder ? 50 : undefined,
|
||||
[]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
!disabled
|
||||
@ -69,8 +67,9 @@ export const useBottomPlaceholder = <T extends {}>({
|
||||
fullWidthCellRenderer,
|
||||
onSortChanged: onRowsChanged,
|
||||
onFilterChanged: onRowsChanged,
|
||||
getRowHeight,
|
||||
}
|
||||
: {},
|
||||
[onBodyScrollEnd, onRowsChanged, disabled]
|
||||
[onBodyScrollEnd, onRowsChanged, disabled, getRowHeight]
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user