feat(trading): view stop order history (#4586)

This commit is contained in:
Bartłomiej Głownia 2023-08-23 13:41:56 +02:00 committed by GitHub
parent 0767139712
commit d3dbdd2bd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 133 additions and 47 deletions

View File

@ -128,6 +128,9 @@ fragment StopOrderFields on StopOrder {
updatedAt updatedAt
partyId partyId
marketId marketId
order {
...OrderFields
}
trigger { trigger {
... on StopOrderPrice { ... on StopOrderPrice {
price price

View File

@ -34,22 +34,51 @@ export type OrdersUpdateSubscription = { __typename?: 'Subscription', orders?: A
export type OrderSubmissionFieldsFragment = { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null }; export type OrderSubmissionFieldsFragment = { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null };
export type StopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } }; export type StopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } };
export type StopOrdersQueryVariables = Types.Exact<{ export type StopOrdersQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID']; partyId: Types.Scalars['ID'];
}>; }>;
export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null }; export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null };
export type StopOrderByIdQueryVariables = Types.Exact<{ export type StopOrderByIdQueryVariables = Types.Exact<{
stopOrderId: Types.Scalars['ID']; stopOrderId: Types.Scalars['ID'];
}>; }>;
export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }; export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, order?: { __typename?: 'Order', id: string, type?: Types.OrderType | null, side: Types.Side, size: string, status: Types.OrderStatus, rejectionReason?: Types.OrderRejectionReason | null, price: string, timeInForce: Types.OrderTimeInForce, remaining: string, expiresAt?: any | null, createdAt: any, updatedAt?: any | null, postOnly?: boolean | null, reduceOnly?: boolean | null, market: { __typename?: 'Market', id: string }, liquidityProvision?: { __typename: 'LiquidityProvision' } | null, peggedOrder?: { __typename: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, icebergOrder?: { __typename: 'IcebergOrder', peakSize: string, minimumVisibleSize: string, reservedRemaining: string } | null } | null, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null };
export const OrderUpdateFieldsFragmentDoc = gql`
fragment OrderUpdateFields on OrderUpdate {
id
marketId
type
side
size
status
rejectionReason
price
timeInForce
remaining
expiresAt
createdAt
updatedAt
liquidityProvisionId
peggedOrder {
__typename
reference
offset
}
icebergOrder {
__typename
peakSize
minimumVisibleSize
reservedRemaining
}
}
`;
export const OrderFieldsFragmentDoc = gql` export const OrderFieldsFragmentDoc = gql`
fragment OrderFields on Order { fragment OrderFields on Order {
id id
@ -85,35 +114,6 @@ export const OrderFieldsFragmentDoc = gql`
} }
} }
`; `;
export const OrderUpdateFieldsFragmentDoc = gql`
fragment OrderUpdateFields on OrderUpdate {
id
marketId
type
side
size
status
rejectionReason
price
timeInForce
remaining
expiresAt
createdAt
updatedAt
liquidityProvisionId
peggedOrder {
__typename
reference
offset
}
icebergOrder {
__typename
peakSize
minimumVisibleSize
reservedRemaining
}
}
`;
export const OrderSubmissionFieldsFragmentDoc = gql` export const OrderSubmissionFieldsFragmentDoc = gql`
fragment OrderSubmissionFields on OrderSubmission { fragment OrderSubmissionFields on OrderSubmission {
marketId marketId
@ -144,6 +144,9 @@ export const StopOrderFieldsFragmentDoc = gql`
updatedAt updatedAt
partyId partyId
marketId marketId
order {
...OrderFields
}
trigger { trigger {
... on StopOrderPrice { ... on StopOrderPrice {
price price
@ -156,7 +159,8 @@ export const StopOrderFieldsFragmentDoc = gql`
...OrderSubmissionFields ...OrderSubmissionFields
} }
} }
${OrderSubmissionFieldsFragmentDoc}`; ${OrderFieldsFragmentDoc}
${OrderSubmissionFieldsFragmentDoc}`;
export const OrderByIdDocument = gql` export const OrderByIdDocument = gql`
query OrderById($orderId: ID!) { query OrderById($orderId: ID!) {
orderByID(id: $orderId) { orderByID(id: $orderId) {

View File

@ -296,7 +296,7 @@ export const OrderListTable = memo<
</ButtonLink> </ButtonLink>
</> </>
)} )}
<ActionsDropdown data-testid="market-actions-content"> <ActionsDropdown data-testid="order-actions-content">
<TradingDropdownCopyItem <TradingDropdownCopyItem
value={data.id} value={data.id}
text={t('Copy order ID')} text={t('Copy order ID')}

View File

@ -1,11 +1,13 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { StopOrdersTable } from '../stop-orders-table/stop-orders-table'; import { StopOrdersTable } from '../stop-orders-table/stop-orders-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid'; import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useVegaTransactionStore } from '@vegaprotocol/wallet'; import { useVegaTransactionStore } from '@vegaprotocol/wallet';
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider'; import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import { stopOrdersWithMarketProvider } from '../order-data-provider/stop-orders-data-provider'; import { stopOrdersWithMarketProvider } from '../order-data-provider/stop-orders-data-provider';
import { OrderViewDialog } from '../order-list/order-view-dialog';
import type { Order } from '../order-data-provider';
export interface StopOrdersManagerProps { export interface StopOrdersManagerProps {
partyId: string; partyId: string;
@ -23,6 +25,7 @@ export const StopOrdersManager = ({
gridProps, gridProps,
}: StopOrdersManagerProps) => { }: StopOrdersManagerProps) => {
const create = useVegaTransactionStore((state) => state.create); const create = useVegaTransactionStore((state) => state.create);
const [viewOrder, setViewOrder] = useState<Order | null>(null);
const variables = { partyId }; const variables = { partyId };
const { data, error, reload } = useDataProvider({ const { data, error, reload } = useDataProvider({
@ -53,14 +56,25 @@ export const StopOrdersManager = ({
); );
return ( return (
<StopOrdersTable <>
rowData={data} <StopOrdersTable
onCancel={cancel} rowData={data}
onMarketClick={onMarketClick} onCancel={cancel}
isReadOnly={isReadOnly} onView={setViewOrder}
suppressAutoSize onMarketClick={onMarketClick}
overlayNoRowsTemplate={error ? error.message : t('No stop orders')} isReadOnly={isReadOnly}
{...gridProps} suppressAutoSize
/> overlayNoRowsTemplate={error ? error.message : t('No stop orders')}
{...gridProps}
/>
{viewOrder && (
<OrderViewDialog
isOpen={Boolean(viewOrder)}
order={viewOrder}
onChange={() => setViewOrder(null)}
onMarketClick={onMarketClick}
/>
)}
</>
); );
}; };

View File

@ -4,6 +4,7 @@ import type { PartialDeep } from 'type-fest';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet'; import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import userEvent from '@testing-library/user-event';
import { import {
StopOrdersTable, StopOrdersTable,
type StopOrdersTableProps, type StopOrdersTableProps,
@ -27,6 +28,7 @@ jest.mock('@vegaprotocol/utils', () => ({
})); }));
const defaultProps: StopOrdersTableProps = { const defaultProps: StopOrdersTableProps = {
onView: jest.fn(),
rowData: [], rowData: [],
onCancel: jest.fn(), onCancel: jest.fn(),
isReadOnly: false, isReadOnly: false,
@ -104,6 +106,7 @@ const rowData = [
generateStopOrder({ generateStopOrder({
id: 'stop-order-6', id: 'stop-order-6',
status: Schema.StopOrderStatus.STATUS_TRIGGERED, status: Schema.StopOrderStatus.STATUS_TRIGGERED,
order: { id: 'order-id' },
}), }),
]; ];
@ -234,4 +237,37 @@ describe('StopOrdersTable', () => {
); );
}); });
}); });
it('shows actions dropdown only for triggered stop orders', async () => {
await act(async () => {
render(generateJsx({ rowData }));
});
const dropdownMenuButtons = screen.getAllByTestId('dropdown-menu');
expect(dropdownMenuButtons).toHaveLength(1);
dropdownMenuButtons.forEach((dropdownMenuButton) => {
const id = dropdownMenuButton
.closest('[role="row"]')
?.getAttribute('row-id');
expect(rowData.find((row) => row.id === id)?.status).toEqual(
Schema.StopOrderStatus.STATUS_TRIGGERED
);
});
});
it('action dropdown has copy and view order actions', async () => {
const onView = jest.fn();
const user = userEvent.setup();
await act(async () => {
render(generateJsx({ rowData, onView }));
});
const dropdownMenuButtons = screen.getByTestId('dropdown-menu');
dropdownMenuButtons.click();
await user.click(dropdownMenuButtons as HTMLButtonElement);
const menuItems = screen.getAllByRole('menuitem');
expect(menuItems).toHaveLength(2);
expect(menuItems[0]).toHaveTextContent('Copy order ID');
expect(menuItems[1]).toHaveTextContent('View order details');
menuItems[1].click();
expect(onView).toBeCalled();
});
}); });

View File

@ -7,7 +7,14 @@ import {
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import {
ActionsDropdown,
ButtonLink,
VegaIcon,
VegaIconNames,
DropdownMenuItem,
TradingDropdownCopyItem,
} from '@vegaprotocol/ui-toolkit';
import type { ForwardedRef } from 'react'; import type { ForwardedRef } from 'react';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { import {
@ -28,6 +35,7 @@ import type {
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider'; import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
import type { ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
import type { Order } from '../order-data-provider';
const defaultColDef = { const defaultColDef = {
resizable: true, resizable: true,
@ -38,12 +46,13 @@ const defaultColDef = {
export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & { export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
onCancel: (order: StopOrder) => void; onCancel: (order: StopOrder) => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void; onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onView: (order: Order) => void;
isReadOnly: boolean; isReadOnly: boolean;
}; };
export const StopOrdersTable = memo< export const StopOrdersTable = memo<
StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> } StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> }
>(({ onCancel, onMarketClick, ...props }: StopOrdersTableProps) => { >(({ onCancel, onView, onMarketClick, ...props }: StopOrdersTableProps) => {
const showAllActions = !props.isReadOnly; const showAllActions = !props.isReadOnly;
const columnDefs: ColDef[] = useMemo( const columnDefs: ColDef[] = useMemo(
() => [ () => [
@ -236,12 +245,32 @@ export const StopOrdersTable = memo<
{t('Cancel')} {t('Cancel')}
</ButtonLink> </ButtonLink>
)} )}
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
data.order && (
<ActionsDropdown data-testid="stop-order-actions-content">
<TradingDropdownCopyItem
value={data.order.id}
text={t('Copy order ID')}
/>
<DropdownMenuItem
key={'view-order'}
data-testid="view-order"
onClick={() =>
data.order &&
onView({ ...data.order, market: data.market })
}
>
<VegaIcon name={VegaIconNames.INFO} size={16} />
{t('View order details')}
</DropdownMenuItem>
</ActionsDropdown>
)}
</div> </div>
); );
}, },
}, },
], ],
[onCancel, onMarketClick, props.isReadOnly, showAllActions] [onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
); );
return ( return (