feat(958): add cancel all orders button (#1861)
This commit is contained in:
parent
13a77d1583
commit
ef07536159
@ -65,8 +65,8 @@ const OrdersManager = () => {
|
||||
defaultColDef={defaultColDef}
|
||||
/>
|
||||
<orderCancel.Dialog
|
||||
title={getCancelDialogTitle(orderCancel.cancelledOrder?.status)}
|
||||
intent={getCancelDialogIntent(orderCancel.cancelledOrder?.status)}
|
||||
title={getCancelDialogTitle(orderCancel)}
|
||||
intent={getCancelDialogIntent(orderCancel)}
|
||||
content={{
|
||||
Complete: (
|
||||
<OrderFeedback
|
||||
|
@ -11,11 +11,8 @@ import {
|
||||
positiveClassNames,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
OrderFieldsFragment,
|
||||
Order,
|
||||
CancelOrderArgs,
|
||||
} from '@vegaprotocol/orders';
|
||||
import type { OrderFieldsFragment, Order } from '@vegaprotocol/orders';
|
||||
import type { OrderCancellationBody } from '@vegaprotocol/wallet';
|
||||
import { isOrderActive } from '@vegaprotocol/orders';
|
||||
import {
|
||||
OrderRejectionReasonMapping,
|
||||
@ -34,7 +31,7 @@ type OrderTimeKey = keyof typeof OrderTimeInForceMapping;
|
||||
interface Props {
|
||||
setEditOrder: (order: Order) => void;
|
||||
orderCancel: {
|
||||
cancel: (args: CancelOrderArgs) => void;
|
||||
cancel: (args: OrderCancellationBody['orderCancellation']) => void;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ const orderPrice = 'price';
|
||||
const orderTimeInForce = 'timeInForce';
|
||||
const orderCreatedAt = 'createdAt';
|
||||
const cancelOrderBtn = 'cancel';
|
||||
const cancelAllOrdersBtn = 'cancelAll';
|
||||
const editOrderBtn = 'edit';
|
||||
|
||||
describe('orders list', { tags: '@smoke' }, () => {
|
||||
@ -29,11 +30,17 @@ describe('orders list', { tags: '@smoke' }, () => {
|
||||
cy.wait('@Orders').then(() => {
|
||||
expect(subscriptionMocks.OrdersUpdate).to.be.calledOnce;
|
||||
});
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
it('renders orders', () => {
|
||||
cy.getByTestId('tab-orders').should('be.visible');
|
||||
|
||||
cy.getByTestId(cancelAllOrdersBtn).should('be.visible');
|
||||
cy.getByTestId(cancelOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId(editOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get(`[role='rowgroup']`)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderSymbol}']`).each(($symbol) => {
|
||||
cy.wrap($symbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
@ -65,14 +72,7 @@ describe('orders list', { tags: '@smoke' }, () => {
|
||||
cy.get(`[col-id='${orderCreatedAt}']`).each(($dateTime) => {
|
||||
cy.wrap($dateTime).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.getByTestId(cancelOrderBtn)
|
||||
.should('be.visible')
|
||||
.and('have.length.at.least', 1);
|
||||
|
||||
cy.getByTestId(editOrderBtn)
|
||||
.should('be.visible')
|
||||
.and('have.length.at.least', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -25,6 +25,7 @@ const defaultProps: OrderListTableProps = {
|
||||
rowData: [],
|
||||
setEditOrder: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
cancelAll: jest.fn(),
|
||||
};
|
||||
|
||||
const generateJsx = (
|
||||
@ -87,8 +88,8 @@ describe('OrderListTable', () => {
|
||||
'-',
|
||||
'Edit',
|
||||
];
|
||||
cells.forEach((cell, i) =>
|
||||
expect(cell).toHaveTextContent(expectedValues[i])
|
||||
expectedValues.forEach((expectedValue, i) =>
|
||||
expect(cells[i]).toHaveTextContent(expectedValue)
|
||||
);
|
||||
});
|
||||
|
||||
@ -112,8 +113,8 @@ describe('OrderListTable', () => {
|
||||
'-',
|
||||
'Edit',
|
||||
];
|
||||
cells.forEach((cell, i) =>
|
||||
expect(cell).toHaveTextContent(expectedValues[i])
|
||||
expectedValues.forEach((expectedValue, i) =>
|
||||
expect(cells[i]).toHaveTextContent(expectedValue)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -19,6 +19,7 @@ const Template: Story = (args) => {
|
||||
<OrderListTable
|
||||
rowData={args.data}
|
||||
cancel={cancel}
|
||||
cancelAll={cancel}
|
||||
setEditOrder={() => {
|
||||
return;
|
||||
}}
|
||||
@ -47,6 +48,7 @@ const Template2: Story = (args) => {
|
||||
<OrderListTable
|
||||
rowData={args.data}
|
||||
cancel={cancel}
|
||||
cancelAll={cancel}
|
||||
setEditOrder={setEditOrder}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
@ -5,6 +6,7 @@ import {
|
||||
negativeClassNames,
|
||||
positiveClassNames,
|
||||
t,
|
||||
truncateByChars,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
OrderRejectionReasonMapping,
|
||||
@ -19,10 +21,12 @@ import {
|
||||
Intent,
|
||||
Link,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { TransactionResult } from '@vegaprotocol/wallet';
|
||||
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { forwardRef, useState } from 'react';
|
||||
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
||||
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
||||
import { OrderFeedback } from '../order-feedback';
|
||||
@ -32,9 +36,46 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { Order } from '../';
|
||||
type OrderListProps = AgGridReactProps;
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import type { OrderEventFieldsFragment } from '../../order-hooks';
|
||||
|
||||
type OrderListProps = TypedDataAgGrid<Order>;
|
||||
|
||||
export const TransactionComplete = ({
|
||||
transaction,
|
||||
transactionResult,
|
||||
}: {
|
||||
transaction: VegaTxState;
|
||||
transactionResult?: TransactionResult;
|
||||
}) => {
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
if (!transactionResult) return null;
|
||||
return (
|
||||
<>
|
||||
{transactionResult.status ? (
|
||||
<p>{t('Transaction successful')}</p>
|
||||
) : (
|
||||
<p className="text-vega-red">
|
||||
{t('Transaction failed')}: {transactionResult.error}
|
||||
</p>
|
||||
)}
|
||||
{transaction.txHash && (
|
||||
<>
|
||||
<p className="font-semibold mt-4">{t('Transaction')}</p>
|
||||
<p>
|
||||
<Link
|
||||
href={`${VEGA_EXPLORER_URL}/txs/${transaction.txHash}`}
|
||||
target="_blank"
|
||||
>
|
||||
{truncateByChars(transaction.txHash)}
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
(props, ref) => {
|
||||
@ -46,7 +87,10 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
<>
|
||||
<OrderListTable
|
||||
{...props}
|
||||
cancel={(order) => {
|
||||
cancelAll={() => {
|
||||
orderCancel.cancel({});
|
||||
}}
|
||||
cancel={(order: Order) => {
|
||||
if (!order.market) return;
|
||||
orderCancel.cancel({
|
||||
orderId: order.id,
|
||||
@ -57,14 +101,16 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
setEditOrder={setEditOrder}
|
||||
/>
|
||||
<orderCancel.Dialog
|
||||
title={getCancelDialogTitle(orderCancel.cancelledOrder?.status)}
|
||||
intent={getCancelDialogIntent(orderCancel.cancelledOrder?.status)}
|
||||
title={getCancelDialogTitle(orderCancel)}
|
||||
intent={getCancelDialogIntent(orderCancel)}
|
||||
content={{
|
||||
Complete: (
|
||||
Complete: orderCancel.cancelledOrder ? (
|
||||
<OrderFeedback
|
||||
transaction={orderCancel.transaction}
|
||||
order={orderCancel.cancelledOrder}
|
||||
/>
|
||||
) : (
|
||||
<TransactionComplete {...orderCancel} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
@ -97,13 +143,14 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
}
|
||||
);
|
||||
|
||||
export type OrderListTableProps = AgGridReactProps & {
|
||||
export type OrderListTableProps = OrderListProps & {
|
||||
cancel: (order: Order) => void;
|
||||
cancelAll: () => void;
|
||||
setEditOrder: (order: Order) => void;
|
||||
};
|
||||
|
||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
({ cancel, setEditOrder, ...props }, ref) => {
|
||||
({ cancel, cancelAll, setEditOrder, ...props }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
@ -112,6 +159,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data.id}
|
||||
rowHeight={34}
|
||||
pinnedBottomRowData={[{}]}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
@ -147,7 +195,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||
if (node?.rowPinned) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value)) {
|
||||
return '-';
|
||||
}
|
||||
@ -167,7 +219,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
valueFormatter={({
|
||||
data: order,
|
||||
value,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'type'>) => {
|
||||
if (node?.rowPinned) {
|
||||
return '';
|
||||
}
|
||||
if (!value) return '-';
|
||||
if (order?.peggedOrder) return t('Pegged');
|
||||
if (order?.liquidityProvision) return t('Liquidity provision');
|
||||
@ -197,7 +253,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
valueFormatter={({
|
||||
data,
|
||||
value,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||
if (node?.rowPinned) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
@ -218,7 +278,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||
if (node?.rowPinned) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.type === Schema.OrderType.TYPE_MARKET ||
|
||||
@ -260,7 +324,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
field="updatedAt"
|
||||
valueFormatter={({
|
||||
value,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'updatedAt'>) => {
|
||||
if (node?.rowPinned) {
|
||||
return '';
|
||||
}
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
@ -268,10 +336,23 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
colId="amend"
|
||||
headerName=""
|
||||
field="status"
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Order>) => {
|
||||
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
|
||||
if (node?.rowPinned) {
|
||||
return (
|
||||
<div className="flex gap-2 items-center h-full justify-end">
|
||||
<Button
|
||||
size="xs"
|
||||
data-testid="cancelAll"
|
||||
onClick={() => cancelAll()}
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isOrderAmendable(data)) {
|
||||
return data ? (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 items-center h-full justify-end">
|
||||
<Button
|
||||
data-testid="edit"
|
||||
onClick={() => setEditOrder(data)}
|
||||
@ -353,32 +434,46 @@ export const getEditDialogTitle = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getCancelDialogIntent = (
|
||||
status?: Schema.OrderStatus
|
||||
): Intent | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case Schema.OrderStatus.STATUS_CANCELLED:
|
||||
export const getCancelDialogIntent = ({
|
||||
cancelledOrder,
|
||||
transactionResult,
|
||||
}: {
|
||||
cancelledOrder: OrderEventFieldsFragment | null;
|
||||
transactionResult?: TransactionResult;
|
||||
}): Intent | undefined => {
|
||||
if (cancelledOrder) {
|
||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
||||
return Intent.Success;
|
||||
default:
|
||||
}
|
||||
return Intent.Danger;
|
||||
}
|
||||
if (transactionResult) {
|
||||
if ('error' in transactionResult && transactionResult.error) {
|
||||
return Intent.Danger;
|
||||
}
|
||||
return Intent.Success;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export const getCancelDialogTitle = (
|
||||
status?: Schema.OrderStatus
|
||||
): string | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case Schema.OrderStatus.STATUS_CANCELLED:
|
||||
export const getCancelDialogTitle = ({
|
||||
cancelledOrder,
|
||||
transactionResult,
|
||||
}: {
|
||||
cancelledOrder: OrderEventFieldsFragment | null;
|
||||
transactionResult?: TransactionResult;
|
||||
}): string | undefined => {
|
||||
if (cancelledOrder) {
|
||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
||||
return t('Order cancelled');
|
||||
default:
|
||||
}
|
||||
return t('Order cancellation failed');
|
||||
}
|
||||
if (transactionResult) {
|
||||
if (transactionResult.status) {
|
||||
return t('Orders cancelled');
|
||||
}
|
||||
return t('Orders not cancelled');
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
@ -1,19 +1,24 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useVegaWallet, useVegaTransaction } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaWallet,
|
||||
useVegaTransaction,
|
||||
useTransactionResult,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
OrderCancellationBody,
|
||||
TransactionResult,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type { OrderEventFieldsFragment } from './';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useOrderEvent } from './use-order-event';
|
||||
|
||||
export interface CancelOrderArgs {
|
||||
orderId: string;
|
||||
marketId: string;
|
||||
}
|
||||
|
||||
export const useOrderCancel = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const [cancelledOrder, setCancelledOrder] =
|
||||
useState<OrderEventFieldsFragment | null>(null);
|
||||
const [transactionResult, setTransactionResult] =
|
||||
useState<TransactionResult>();
|
||||
|
||||
const {
|
||||
send,
|
||||
@ -24,6 +29,7 @@ export const useOrderCancel = () => {
|
||||
} = useVegaTransaction();
|
||||
|
||||
const waitForOrderEvent = useOrderEvent(transaction);
|
||||
const waitForTransactionResult = useTransactionResult();
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
@ -31,7 +37,7 @@ export const useOrderCancel = () => {
|
||||
}, [resetTransaction]);
|
||||
|
||||
const cancel = useCallback(
|
||||
async (args: CancelOrderArgs) => {
|
||||
async (orderCancellation: OrderCancellationBody['orderCancellation']) => {
|
||||
if (!pubKey) {
|
||||
return;
|
||||
}
|
||||
@ -39,26 +45,36 @@ export const useOrderCancel = () => {
|
||||
setCancelledOrder(null);
|
||||
|
||||
try {
|
||||
await send(pubKey, {
|
||||
orderCancellation: {
|
||||
orderId: args.orderId,
|
||||
marketId: args.marketId,
|
||||
},
|
||||
const res = await send(pubKey, {
|
||||
orderCancellation,
|
||||
});
|
||||
|
||||
const cancelledOrder = await waitForOrderEvent(args.orderId, pubKey);
|
||||
if (orderCancellation.orderId) {
|
||||
const cancelledOrder = await waitForOrderEvent(
|
||||
orderCancellation.orderId,
|
||||
pubKey
|
||||
);
|
||||
setCancelledOrder(cancelledOrder);
|
||||
setComplete();
|
||||
} else if (res) {
|
||||
const txResult = await waitForTransactionResult(
|
||||
res.transactionHash,
|
||||
pubKey
|
||||
);
|
||||
setTransactionResult(txResult);
|
||||
setComplete();
|
||||
}
|
||||
return res;
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[pubKey, send, setComplete, waitForOrderEvent]
|
||||
[pubKey, send, setComplete, waitForOrderEvent, waitForTransactionResult]
|
||||
);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
transactionResult,
|
||||
cancelledOrder,
|
||||
Dialog,
|
||||
cancel,
|
||||
|
@ -27,8 +27,8 @@ interface OrderSubmission {
|
||||
}
|
||||
|
||||
interface OrderCancellation {
|
||||
orderId: string;
|
||||
marketId: string;
|
||||
orderId?: string;
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
interface OrderAmendment {
|
||||
|
Loading…
Reference in New Issue
Block a user