fix(orders): orders table flickering (#3105)
This commit is contained in:
parent
ac163d0194
commit
f10c33748f
@ -1,7 +1,7 @@
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Notification, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { DepositDialog, useDepositDialog } from '@vegaprotocol/deposits';
|
||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||
|
||||
interface Props {
|
||||
margin: string;
|
||||
@ -16,25 +16,22 @@ interface Props {
|
||||
export const MarginWarning = ({ margin, balance, asset }: Props) => {
|
||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||
return (
|
||||
<>
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
testId="dealticket-warning-margin"
|
||||
message={`You may not have enough margin available to open this position. ${formatNumber(
|
||||
margin,
|
||||
asset.decimals
|
||||
)} ${asset.symbol} ${t(
|
||||
'is currently required. You have only'
|
||||
)} ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t(
|
||||
'available.'
|
||||
)}`}
|
||||
buttonProps={{
|
||||
text: t(`Deposit ${asset.symbol}`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
}}
|
||||
/>
|
||||
<DepositDialog />
|
||||
</>
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
testId="dealticket-warning-margin"
|
||||
message={`You may not have enough margin available to open this position. ${formatNumber(
|
||||
margin,
|
||||
asset.decimals
|
||||
)} ${asset.symbol} ${t(
|
||||
'is currently required. You have only'
|
||||
)} ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t(
|
||||
'available.'
|
||||
)}`}
|
||||
buttonProps={{
|
||||
text: t(`Deposit ${asset.symbol}`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { marketsProvider } from '@vegaprotocol/market-list';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/utils';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import type {
|
||||
OrderFieldsFragment,
|
||||
OrderUpdateFieldsFragment,
|
||||
@ -50,6 +51,9 @@ const orderMatchFilters = (
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (variables?.filter?.excludeLiquidity && order.liquidityProvisionId) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.dateRange?.start &&
|
||||
!(
|
||||
@ -177,3 +181,57 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
combineDelta<Order, ReturnType<typeof getDelta>['0']>,
|
||||
combineInsertionData<Order>
|
||||
);
|
||||
|
||||
const hasActiveOrderProviderInternal = makeDataProvider({
|
||||
query: OrdersDocument,
|
||||
subscriptionQuery: OrdersUpdateDocument,
|
||||
update: (
|
||||
data: boolean | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
reload: () => void
|
||||
) => {
|
||||
const orders = delta?.filter(
|
||||
(order) => !(order.peggedOrder || order.liquidityProvisionId)
|
||||
);
|
||||
if (!orders?.length) {
|
||||
return data;
|
||||
}
|
||||
const hasActiveOrders = orders.some(
|
||||
(order) => order.status === OrderStatus.STATUS_ACTIVE
|
||||
);
|
||||
if (hasActiveOrders) {
|
||||
return true;
|
||||
} else if (data && !hasActiveOrders) {
|
||||
reload();
|
||||
}
|
||||
return data;
|
||||
},
|
||||
getData: (responseData: OrdersQuery | null) => {
|
||||
const hasActiveOrder = !!responseData?.party?.ordersConnection?.edges?.some(
|
||||
(order) => !(order.node.peggedOrder || order.node.liquidityProvision)
|
||||
);
|
||||
return hasActiveOrder;
|
||||
},
|
||||
getDelta,
|
||||
});
|
||||
|
||||
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||
boolean,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
hasActiveOrderProviderInternal(callback, client, {
|
||||
filter: {
|
||||
status: [OrderStatus.STATUS_ACTIVE],
|
||||
excludeLiquidity: true,
|
||||
},
|
||||
pagination: {
|
||||
first: 1,
|
||||
},
|
||||
...variables,
|
||||
} as OrdersQueryVariables),
|
||||
],
|
||||
(parts) => parts[0]
|
||||
);
|
||||
|
@ -74,6 +74,27 @@ export const TransactionComplete = ({
|
||||
);
|
||||
};
|
||||
|
||||
const CancelAllOrdersButton = ({
|
||||
onClick,
|
||||
marketId,
|
||||
}: {
|
||||
onClick: (marketId?: string) => void;
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||
return hasActiveOrder ? (
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => onClick(marketId)}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const OrderListManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
@ -86,7 +107,6 @@ export const OrderListManager = ({
|
||||
const [filter, setFilter] = useState<Filter | undefined>();
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||
|
||||
const { data, error, loading, addNewRows, getRows, reload } =
|
||||
useOrderListData({
|
||||
@ -140,7 +160,7 @@ export const OrderListManager = ({
|
||||
[setSort]
|
||||
);
|
||||
|
||||
const onCancel = useCallback(
|
||||
const cancel = useCallback(
|
||||
(order: Order) => {
|
||||
if (!order.market) return;
|
||||
create({
|
||||
@ -153,6 +173,17 @@ export const OrderListManager = ({
|
||||
[create]
|
||||
);
|
||||
|
||||
const cancelAll = useCallback(
|
||||
(marketId?: string) => {
|
||||
create({
|
||||
orderCancellation: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[create]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
||||
@ -165,11 +196,10 @@ export const OrderListManager = ({
|
||||
onBodyScroll={onBodyScroll}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onSortChanged={onSortChange}
|
||||
cancel={onCancel}
|
||||
cancel={cancel}
|
||||
setEditOrder={setEditOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
hasActiveOrder={hasActiveOrder}
|
||||
blockLoadDebounceMillis={100}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
@ -185,22 +215,8 @@ export const OrderListManager = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isReadOnly && hasActiveOrder && (
|
||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
create({
|
||||
orderCancellation: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<CancelAllOrdersButton onClick={cancelAll} marketId={marketId} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
@ -31,11 +31,10 @@ export type OrderListTableProps = OrderListProps & {
|
||||
setEditOrder: (order: Order) => void;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
hasActiveOrder?: boolean;
|
||||
};
|
||||
|
||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
({ cancel, setEditOrder, onMarketClick, hasActiveOrder, ...props }, ref) => {
|
||||
({ cancel, setEditOrder, onMarketClick, ...props }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
@ -47,7 +46,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: hasActiveOrder ? 'calc(100% - 46px)' : '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
{...props}
|
||||
|
@ -1,46 +1,24 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
useOrdersUpdateSubscription,
|
||||
useOrdersQuery,
|
||||
} from '../components/order-data-provider/__generated__/Orders';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { hasActiveOrderProvider } from '../components/order-data-provider/';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const useHasActiveOrder = (marketId?: string) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const skip = !pubKey;
|
||||
const [hasActiveOrder, setHasActiveOrder] = useState(false);
|
||||
const subscriptionVariables = useMemo(
|
||||
() => ({
|
||||
const update = useCallback(({ data }: { data: boolean | null }) => {
|
||||
console.log({ data });
|
||||
setHasActiveOrder(!!data);
|
||||
return true;
|
||||
}, []);
|
||||
useDataProvider({
|
||||
dataProvider: hasActiveOrderProvider,
|
||||
update,
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
marketId,
|
||||
}),
|
||||
[pubKey, marketId]
|
||||
);
|
||||
const queryVariables = useMemo(
|
||||
() => ({
|
||||
...subscriptionVariables,
|
||||
pagination: { first: 1 },
|
||||
filter: { status: [Schema.OrderStatus.STATUS_ACTIVE] },
|
||||
}),
|
||||
[subscriptionVariables]
|
||||
);
|
||||
|
||||
const { refetch, data, loading } = useOrdersQuery({
|
||||
variables: queryVariables,
|
||||
fetchPolicy: 'no-cache',
|
||||
skip,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!loading && data) {
|
||||
setHasActiveOrder(!!data.party?.ordersConnection?.edges?.length);
|
||||
}
|
||||
}, [loading, data]);
|
||||
|
||||
useOrdersUpdateSubscription({
|
||||
variables: subscriptionVariables,
|
||||
onData: () => refetch(),
|
||||
skip,
|
||||
},
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
return hasActiveOrder;
|
||||
|
@ -62,8 +62,8 @@ describe('useDataProvider hook', () => {
|
||||
});
|
||||
expect(result.current.data).toEqual(updateCallbackPayload.data);
|
||||
expect(result.current.loading).toEqual(false);
|
||||
expect(update).toBeCalledTimes(1);
|
||||
expect(update.mock.calls[0][0].data).toEqual(updateCallbackPayload.data);
|
||||
expect(update).toBeCalledTimes(2);
|
||||
expect(update.mock.calls[1][0].data).toEqual(updateCallbackPayload.data);
|
||||
});
|
||||
|
||||
it('calls update on error', async () => {
|
||||
@ -77,8 +77,8 @@ describe('useDataProvider hook', () => {
|
||||
});
|
||||
expect(result.current.data).toEqual(updateCallbackPayload.data);
|
||||
expect(result.current.loading).toEqual(false);
|
||||
expect(update).toBeCalledTimes(1);
|
||||
expect(update.mock.calls[0][0].data).toEqual(updateCallbackPayload.data);
|
||||
expect(update).toBeCalledTimes(2);
|
||||
expect(update.mock.calls[1][0].data).toEqual(updateCallbackPayload.data);
|
||||
});
|
||||
|
||||
it('calls update if isUpdate and skip setting state if update returns true', async () => {
|
||||
@ -89,7 +89,8 @@ describe('useDataProvider hook', () => {
|
||||
await act(async () => {
|
||||
callback({ ...updateCallbackPayload, data: ++data });
|
||||
});
|
||||
expect(update).toBeCalledTimes(1);
|
||||
expect(update).toBeCalledTimes(2);
|
||||
expect(result.current.data).toEqual(data);
|
||||
await act(async () => {
|
||||
callback({
|
||||
...updateCallbackPayload,
|
||||
@ -99,9 +100,9 @@ describe('useDataProvider hook', () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.data).toEqual(data);
|
||||
expect(update).toBeCalledTimes(2);
|
||||
expect(update.mock.calls[1][0].data).toEqual(data);
|
||||
expect(update.mock.calls[1][0].delta).toEqual(delta);
|
||||
expect(update).toBeCalledTimes(3);
|
||||
expect(update.mock.calls[2][0].data).toEqual(data);
|
||||
expect(update.mock.calls[2][0].delta).toEqual(delta);
|
||||
update.mockReturnValueOnce(true);
|
||||
await act(async () => {
|
||||
callback({
|
||||
@ -111,10 +112,10 @@ describe('useDataProvider hook', () => {
|
||||
isUpdate: true,
|
||||
});
|
||||
});
|
||||
expect(result.current.data).toEqual(update.mock.calls[1][0].data);
|
||||
expect(update).toBeCalledTimes(3);
|
||||
expect(update.mock.calls[2][0].data).toEqual(data);
|
||||
expect(update.mock.calls[2][0].delta).toEqual(delta);
|
||||
expect(result.current.data).toEqual(update.mock.calls[2][0].data);
|
||||
expect(update).toBeCalledTimes(4);
|
||||
expect(update.mock.calls[3][0].data).toEqual(data);
|
||||
expect(update.mock.calls[3][0].delta).toEqual(delta);
|
||||
});
|
||||
|
||||
it('calls insert if isInsert and skip setting state if update returns true', async () => {
|
||||
@ -159,7 +160,7 @@ describe('useDataProvider hook', () => {
|
||||
await act(async () => {
|
||||
callback({ ...updateCallbackPayload });
|
||||
});
|
||||
expect(update).toBeCalledTimes(1);
|
||||
expect(update).toBeCalledTimes(2);
|
||||
|
||||
// setting same variables, with different object reference
|
||||
await act(async () => {
|
||||
@ -180,7 +181,7 @@ describe('useDataProvider hook', () => {
|
||||
await act(async () => {
|
||||
callback({ ...updateCallbackPayload });
|
||||
});
|
||||
expect(update).toBeCalledTimes(3);
|
||||
expect(update).toBeCalledTimes(4);
|
||||
|
||||
// changing variables, apollo query will return error
|
||||
await act(async () => {
|
||||
@ -200,7 +201,7 @@ describe('useDataProvider hook', () => {
|
||||
pageInfo: null,
|
||||
});
|
||||
});
|
||||
expect(update).toBeCalledTimes(5);
|
||||
expect(update).toBeCalledTimes(6);
|
||||
});
|
||||
|
||||
it('do not create data provider instance when skip is true', async () => {
|
||||
|
@ -62,7 +62,6 @@ export const useDataProvider = <
|
||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
||||
const loadRef = useRef<Load<Data> | undefined>(undefined);
|
||||
const initialized = useRef<boolean>(false);
|
||||
const prevVariables = usePrevious(props.variables);
|
||||
const [variables, setVariables] = useState(props.variables);
|
||||
useEffect(() => {
|
||||
@ -76,7 +75,6 @@ export const useDataProvider = <
|
||||
}
|
||||
}, []);
|
||||
const reload = useCallback((force = false) => {
|
||||
initialized.current = false;
|
||||
if (reloadRef.current) {
|
||||
reloadRef.current(force);
|
||||
}
|
||||
@ -118,11 +116,8 @@ export const useDataProvider = <
|
||||
}
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
if (!loading && !initialized.current) {
|
||||
initialized.current = true;
|
||||
if (update) {
|
||||
update({ data });
|
||||
}
|
||||
if (!loading && !isUpdate && update) {
|
||||
update({ data });
|
||||
}
|
||||
},
|
||||
[update, insert, skipUpdates]
|
||||
@ -131,10 +126,9 @@ export const useDataProvider = <
|
||||
setData(null);
|
||||
setError(undefined);
|
||||
setTotalCount(undefined);
|
||||
if (initialized.current && update) {
|
||||
if (update) {
|
||||
update({ data: null });
|
||||
}
|
||||
initialized.current = false;
|
||||
if (skip) {
|
||||
setLoading(false);
|
||||
if (update) {
|
||||
@ -157,7 +151,7 @@ export const useDataProvider = <
|
||||
loadRef.current = undefined;
|
||||
return unsubscribe();
|
||||
};
|
||||
}, [client, initialized, dataProvider, callback, variables, skip, update]);
|
||||
}, [client, dataProvider, callback, variables, skip, update]);
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
|
Loading…
Reference in New Issue
Block a user