Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function useOrderBaseProgressBarProps(params: UseOrderProgressBarPropsParams): U
// Do not build progress bar data when these conditions are set
const disableProgressBar = widgetDisabled || isCreating || isFailed || isPresignaturePending || featureFlagDisabled

const orderId = order?.id || ''
const orderId = order?.id

const getCancelOrder = useCancelOrder()
const showCancellationModal = order && getCancelOrder ? getCancelOrder(order) : null
Expand Down Expand Up @@ -254,9 +254,9 @@ function getDoNotQueryStatusEndpoint(

const DEFAULT_STATE = {}

function useGetExecutingOrderState(orderId: string): OrderProgressBarState {
function useGetExecutingOrderState(orderId?: string): OrderProgressBarState {
const fullState = useAtomValue(ordersProgressBarStateAtom)
const singleState = fullState[orderId]
const singleState = orderId ? fullState[orderId] : undefined

return useMemo(() => singleState || DEFAULT_STATE, [singleState])
}
Expand All @@ -281,14 +281,18 @@ function useSetExecutingOrderProgressBarStepNameCallback(): (orderId: string, va
// local updaters

function useCountdownStartUpdater(
orderId: string,
orderId: string | undefined,
countdown: OrderProgressBarState['countdown'],
backendApiStatus: OrderProgressBarState['backendApiStatus'],
shouldDisableCountdown: boolean,
): void {
const setCountdown = useSetExecutingOrderCountdownCallback()

useEffect(() => {
if (!orderId) {
return
}

if (shouldDisableCountdown) {
// Loose `!= null` on purpose: both null and undefined should reset the countdown, but 0 must stay; strict `!== null` would let undefined slip through
if (countdown != null) {
Expand All @@ -308,17 +312,21 @@ function useCountdownStartUpdater(
}, [backendApiStatus, setCountdown, countdown, orderId, shouldDisableCountdown])
}

function useCancellingOrderUpdater(orderId: string, isCancelling: boolean): void {
function useCancellingOrderUpdater(orderId: string | undefined, isCancelling: boolean): void {
const setCancellationTriggered = useSetAtom(setOrderProgressBarCancellationTriggered)

useEffect(() => {
if (isCancelling) setCancellationTriggered(orderId)
if (!orderId || !isCancelling) {
return
}

setCancellationTriggered(orderId)
}, [orderId, isCancelling, setCancellationTriggered])
}

// TODO: Break down this large function into smaller functions
function useProgressBarStepNameUpdater(
orderId: string,
orderId: string | undefined,
isUnfillable: boolean,
isCancelled: boolean,
isExpired: boolean,
Expand Down Expand Up @@ -352,8 +360,14 @@ function useProgressBarStepNameUpdater(

// Update state with new step name
useEffect(() => {
if (!orderId) {
return
}

const ensuredOrderId = orderId

function updateStepName(name: OrderProgressBarStepName): void {
setProgressBarStepName(orderId, name || DEFAULT_STEP_NAME)
setProgressBarStepName(ensuredOrderId, name || DEFAULT_STEP_NAME)
}

let timer: NodeJS.Timeout | undefined
Expand Down Expand Up @@ -487,7 +501,11 @@ const BACKEND_TYPE_TO_PROGRESS_BAR_STEP_NAME: Record<CompetitionOrderStatus.type
[CompetitionOrderStatus.type.CANCELLED]: OrderProgressBarStepName.INITIAL, // TODO: maybe add another state for finished with error?
}

function useBackendApiStatusUpdater(chainId: SupportedChainId, orderId: string, doNotQuery: boolean): void {
function useBackendApiStatusUpdater(
chainId: SupportedChainId,
orderId: string | undefined,
doNotQuery: boolean,
): void {
const setAtom = useSetAtom(updateOrderProgressBarBackendInfo)
const [stopQuerying, setStopQuerying] = useState(false)
const { type: backendApiStatus, value } = usePendingOrderStatus(chainId, orderId, stopQuerying) || {}
Expand Down Expand Up @@ -525,7 +543,7 @@ const POOLING_SWR_OPTIONS = {

function usePendingOrderStatus(
chainId: SupportedChainId,
orderId: string,
orderId: string | undefined,
doNotQuery?: boolean,
): CompetitionOrderStatus | undefined {
return useSWR(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createStore } from 'jotai'

import {
cancellationTrackedOrderIdsAtom,
ordersProgressBarStateAtom,
pruneOrdersProgressBarState,
updateOrderProgressBarCountdown,
Expand Down Expand Up @@ -49,6 +50,7 @@ describe('pruneOrdersProgressBarState', () => {

expect(store.get(ordersProgressBarStateAtom)).toBe(initialState)
})

})

describe('updateOrderProgressBarCountdown', () => {
Expand Down Expand Up @@ -124,3 +126,16 @@ describe('updateOrderProgressBarCountdown', () => {
expect(store.get(ordersProgressBarStateAtom)).toEqual({})
})
})

describe('cancellationTrackedOrderIdsAtom', () => {
it('returns ids with cancellationTriggered flag set', () => {
const store = createStore()
store.set(ordersProgressBarStateAtom, {
a: { cancellationTriggered: true },
b: {},
c: { cancellationTriggered: true },
})

expect(store.get(cancellationTrackedOrderIdsAtom)).toEqual(['a', 'c'])
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const pruneOrdersProgressBarState = atom(null, (get, set, trackedOrderIds
return acc
}, {})

if (!changed && trackedOrderIds.every((orderId) => orderId in fullState)) {
if (!changed) {
return
}

Expand Down Expand Up @@ -213,3 +213,11 @@ export const setOrderProgressBarCancellationTriggered = atom(null, (get, set, or

set(ordersProgressBarStateAtom, { ...fullState, [orderId]: singleState })
})

export const cancellationTrackedOrderIdsAtom = atom((get) => {
const fullState = get(ordersProgressBarStateAtom)

return Object.entries(fullState)
.filter(([, state]) => state?.cancellationTriggered)
.map(([orderId]) => orderId)
})
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jest.mock('modules/trade', () => ({
}))

const mockPruneOrders = jest.fn()
const mockCancellationIds = jest.fn()

jest.mock('jotai', () => {
const actual = jest.requireActual('jotai')
Expand All @@ -51,6 +52,15 @@ jest.mock('jotai', () => {

return actual.useSetAtom(atom)
}),
useAtomValue: jest.fn((atom: PrimitiveAtom<unknown>) => {
const { cancellationTrackedOrderIdsAtom } = jest.requireActual('../state/atoms')

if (atom === cancellationTrackedOrderIdsAtom) {
return mockCancellationIds()
}

return actual.useAtomValue(atom)
}),
}
})

Expand All @@ -72,6 +82,7 @@ describe('OrderProgressStateUpdater', () => {
})
useSurplusQueueOrderIdsMock.mockReturnValue([])
useTradeConfirmStateMock.mockReturnValue({ transactionHash: null } as never)
mockCancellationIds.mockReturnValue([])
})

afterEach(() => {
Expand Down Expand Up @@ -154,4 +165,17 @@ describe('OrderProgressStateUpdater', () => {

expect(mockPruneOrders).toHaveBeenLastCalledWith(['1', '2', '3'])
})

it('keeps cancellation-triggered orders even if they are not pending anymore', () => {
useWalletInfoMock.mockReturnValue({
chainId: undefined,
account: undefined,
} as unknown as WalletInfo)
useOnlyPendingOrdersMock.mockReturnValue([])
mockCancellationIds.mockReturnValue(['abc'])

render(<OrderProgressStateUpdater />)

expect(mockPruneOrders).toHaveBeenLastCalledWith(['abc'])
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSetAtom } from 'jotai'
import { useAtomValue, useSetAtom } from 'jotai'
import { ReactNode, useEffect, useMemo } from 'react'

import { OrderClass, SupportedChainId } from '@cowprotocol/cow-sdk'
Expand All @@ -12,7 +12,7 @@ import { useOnlyPendingOrders } from 'legacy/state/orders/hooks'
import { useTradeConfirmState } from 'modules/trade'

import { useOrderProgressBarProps } from '../hooks/useOrderProgressBarProps'
import { pruneOrdersProgressBarState } from '../state/atoms'
import { cancellationTrackedOrderIdsAtom, pruneOrdersProgressBarState } from '../state/atoms'

function OrderProgressStateObserver({ chainId, order }: { chainId: SupportedChainId; order: Order }): null {
useOrderProgressBarProps(chainId, order)
Expand All @@ -24,6 +24,7 @@ export function OrderProgressStateUpdater(): ReactNode {
const pruneProgressState = useSetAtom(pruneOrdersProgressBarState)
const { transactionHash } = useTradeConfirmState()
const surplusQueueOrderIds = useSurplusQueueOrderIds()
const cancellationTrackedOrderIds = useAtomValue(cancellationTrackedOrderIdsAtom)

const pendingOrders = useOnlyPendingOrders(chainId as SupportedChainId, account)
const marketOrders = useMemo(
Expand All @@ -46,8 +47,18 @@ export function OrderProgressStateUpdater(): ReactNode {
trackedIdsSet.add(transactionHash)
}

cancellationTrackedOrderIds.forEach((orderId) => trackedIdsSet.add(orderId))

pruneProgressState(Array.from(trackedIdsSet))
}, [account, chainId, marketOrders, pruneProgressState, surplusQueueOrderIds, transactionHash])
}, [
account,
cancellationTrackedOrderIds,
chainId,
marketOrders,
pruneProgressState,
surplusQueueOrderIds,
transactionHash,
])

if (!chainId || !account) {
return null
Expand Down
Loading