From f772dade7f86d2f951d684ba5ddb9fbeb175a983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Mon, 4 Sep 2023 12:03:49 +0200 Subject: [PATCH] feat: show maxmimum number of active stop orders limit warning --- .../deal-ticket-stop-order.spec.tsx | 32 +++++++++++++++ .../deal-ticket/deal-ticket-stop-order.tsx | 41 ++++++++++++++++++- .../order-data-provider/Orders.graphql | 4 +- .../__generated__/Orders.ts | 10 ++--- .../components/order-data-provider/index.ts | 1 + .../stop-orders-manager.tsx | 8 +++- 6 files changed, 86 insertions(+), 10 deletions(-) diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx index c15023da1..bdaaf081e 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.spec.tsx @@ -78,8 +78,18 @@ const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price'; const triggerTrailingPercentOffsetErrorMessage = 'stop-order-error-message-trigger-trailing-percent-offset'; +const numberOfActiveOrdersLimit = 'stop-order-warning-limit'; + const ocoPostfix = (id: string, postfix = true) => (postfix ? `${id}-oco` : id); +const mockDataProvider = jest.fn((...args) => ({ + data: Array(0), +})); +jest.mock('@vegaprotocol/data-provider', () => ({ + ...jest.requireActual('@vegaprotocol/data-provider'), + useDataProvider: jest.fn((...args) => mockDataProvider(...args)), +})); + describe('StopOrder', () => { beforeEach(() => { localStorage.clear(); @@ -460,4 +470,26 @@ describe('StopOrder', () => { new Date(screen.getByTestId(datePicker).value).getTime() ).toEqual(now); }); + + it('shows limit of active stop orders number', async () => { + mockDataProvider.mockReturnValue({ + data: Array(4), + }); + render(generateJsx()); + expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(true); + await userEvent.type(screen.getByTestId(sizeInput), '0.01'); + expect(mockDataProvider.mock.lastCall?.[0].skip).toBe(false); + expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument(); + }); + + it('counts oco as two orders', async () => { + mockDataProvider.mockReturnValue({ + data: Array(3), + }); + render(generateJsx()); + await userEvent.type(screen.getByTestId(sizeInput), '0.01'); + expect(screen.queryByTestId(numberOfActiveOrdersLimit)).toBeNull(); + await userEvent.click(screen.getByTestId(oco)); + expect(screen.getByTestId(numberOfActiveOrdersLimit)).toBeInTheDocument(); + }); }); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx index cf9368dad..8abb895bb 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx @@ -26,6 +26,7 @@ import { TradingButton as Button, Pill, Intent, + Notification, } from '@vegaprotocol/ui-toolkit'; import { getDerivedPrice } from '@vegaprotocol/markets'; import type { Market } from '@vegaprotocol/markets'; @@ -53,6 +54,8 @@ import { DealTicketFeeDetails } from './deal-ticket-fee-details'; import { validateExpiration } from '../../utils'; import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants'; import { KeyValue } from './key-value'; +import { useDataProvider } from '@vegaprotocol/data-provider'; +import { stopOrdersProvider } from '@vegaprotocol/orders'; export interface StopOrderProps { market: Market; @@ -60,6 +63,8 @@ export interface StopOrderProps { submit: (order: StopOrdersSubmission) => void; } +const MAX_NUMBER_OF_ACTIVE_STOP_ORDERS = 4; +const POLLING_TIME = 2000; const trailingPercentOffsetStep = '0.1'; const getDefaultValues = ( @@ -802,6 +807,27 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => { const triggerTrailingPercentOffset = watch('triggerTrailingPercentOffset'); const triggerType = watch('triggerType'); + const { data: activeStopOrders, reload } = useDataProvider({ + dataProvider: stopOrdersProvider, + variables: { + filter: { + parties: pubKey ? [pubKey] : [], + markets: [market.id], + liveOnly: true, + }, + }, + skip: !(pubKey && (formState.isDirty || formState.submitCount)), + }); + + useEffect(() => { + const interval = setInterval(() => { + reload(); + }, POLLING_TIME); + return () => { + clearInterval(interval); + }; + }, [reload]); + useEffect(() => { const storedSize = storedFormValues?.[dealTicketType]?.size; if (storedSize && size !== storedSize) { @@ -864,7 +890,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => { {errors.type.message} )} - { )} + {(activeStopOrders?.length ?? 0) + (oco ? 2 : 1) > + MAX_NUMBER_OF_ACTIVE_STOP_ORDERS ? ( +
+ +
+ ) : null} + ; }>; @@ -283,8 +283,8 @@ export function useOrdersUpdateSubscription(baseOptions: Apollo.SubscriptionHook export type OrdersUpdateSubscriptionHookResult = ReturnType; export type OrdersUpdateSubscriptionResult = Apollo.SubscriptionResult; export const StopOrdersDocument = gql` - query StopOrders($partyId: ID!) { - stopOrders(filter: {parties: [$partyId]}) { + query StopOrders($filter: StopOrderFilter) { + stopOrders(filter: $filter) { edges { node { ...StopOrderFields @@ -306,11 +306,11 @@ export const StopOrdersDocument = gql` * @example * const { data, loading, error } = useStopOrdersQuery({ * variables: { - * partyId: // value for 'partyId' + * filter: // value for 'filter' * }, * }); */ -export function useStopOrdersQuery(baseOptions: Apollo.QueryHookOptions) { +export function useStopOrdersQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} return Apollo.useQuery(StopOrdersDocument, options); } diff --git a/libs/orders/src/lib/components/order-data-provider/index.ts b/libs/orders/src/lib/components/order-data-provider/index.ts index 4d19cff7d..7a47c5953 100644 --- a/libs/orders/src/lib/components/order-data-provider/index.ts +++ b/libs/orders/src/lib/components/order-data-provider/index.ts @@ -1,2 +1,3 @@ export * from './__generated__/Orders'; export * from './order-data-provider'; +export * from './stop-orders-data-provider'; diff --git a/libs/orders/src/lib/components/stop-orders-manager/stop-orders-manager.tsx b/libs/orders/src/lib/components/stop-orders-manager/stop-orders-manager.tsx index 1ad2aaf5f..4e1f8f731 100644 --- a/libs/orders/src/lib/components/stop-orders-manager/stop-orders-manager.tsx +++ b/libs/orders/src/lib/components/stop-orders-manager/stop-orders-manager.tsx @@ -7,7 +7,7 @@ import type { StopOrder } from '../order-data-provider/stop-orders-data-provider import { useDataProvider } from '@vegaprotocol/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'; +import type { Order, StopOrdersQueryVariables } from '../order-data-provider'; export interface StopOrdersManagerProps { partyId: string; @@ -26,7 +26,11 @@ export const StopOrdersManager = ({ }: StopOrdersManagerProps) => { const create = useVegaTransactionStore((state) => state.create); const [viewOrder, setViewOrder] = useState(null); - const variables = { partyId }; + const variables: StopOrdersQueryVariables = { + filter: { + parties: [partyId], + }, + }; const { data, error, reload } = useDataProvider({ dataProvider: stopOrdersWithMarketProvider,