feat(958): add cancel all orders button (#1861)

This commit is contained in:
Bartłomiej Głownia 2022-10-28 17:15:21 +02:00 committed by GitHub
parent 13a77d1583
commit ef07536159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 208 additions and 97 deletions

View File

@ -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

View File

@ -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;
};
}

View File

@ -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,50 +30,49 @@ 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(`[col-id='${orderSymbol}']`).each(($symbol) => {
cy.wrap($symbol).invoke('text').should('not.be.empty');
});
cy.get(`[role='rowgroup']`)
.first()
.within(() => {
cy.get(`[col-id='${orderSymbol}']`).each(($symbol) => {
cy.wrap($symbol).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderSize}']`).each(($size) => {
cy.wrap($size).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderSize}']`).each(($size) => {
cy.wrap($size).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderType}']`).each(($type) => {
cy.wrap($type).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderType}']`).each(($type) => {
cy.wrap($type).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderStatus}']`).each(($status) => {
cy.wrap($status).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderStatus}']`).each(($status) => {
cy.wrap($status).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderRemaining}']`).each(($remaining) => {
cy.wrap($remaining).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderRemaining}']`).each(($remaining) => {
cy.wrap($remaining).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderPrice}']`).each(($price) => {
cy.wrap($price).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderPrice}']`).each(($price) => {
cy.wrap($price).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderTimeInForce}']`).each(($timeInForce) => {
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${orderTimeInForce}']`).each(($timeInForce) => {
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
});
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);
cy.get(`[col-id='${orderCreatedAt}']`).each(($dateTime) => {
cy.wrap($dateTime).invoke('text').should('not.be.empty');
});
});
});
});

View File

@ -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)
);
});

View File

@ -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>

View File

@ -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');
}
return t('Order cancellation failed');
}
if (transactionResult) {
if (transactionResult.status) {
return t('Orders cancelled');
}
return t('Orders not cancelled');
}
return;
};

View File

@ -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);
setCancelledOrder(cancelledOrder);
setComplete();
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,

View File

@ -27,8 +27,8 @@ interface OrderSubmission {
}
interface OrderCancellation {
orderId: string;
marketId: string;
orderId?: string;
marketId?: string;
}
interface OrderAmendment {