chore(trading): maintain always up to date active orders list (#3430)
This commit is contained in:
parent
20507ecdd1
commit
31859cb779
@ -33,6 +33,8 @@ import { ViewingBanner } from '../components/viewing-banner';
|
|||||||
import { Banner } from '../components/banner';
|
import { Banner } from '../components/banner';
|
||||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||||
import { Navbar } from '../components/navbar';
|
import { Navbar } from '../components/navbar';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||||
|
|
||||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||||
|
|
||||||
@ -95,6 +97,7 @@ function AppBody({ Component }: AppProps) {
|
|||||||
<ToastsManager />
|
<ToastsManager />
|
||||||
<InitializeHandlers />
|
<InitializeHandlers />
|
||||||
<MaybeConnectEagerly />
|
<MaybeConnectEagerly />
|
||||||
|
<PartyData />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -127,6 +130,18 @@ function VegaTradingApp(props: AppProps) {
|
|||||||
|
|
||||||
export default VegaTradingApp;
|
export default VegaTradingApp;
|
||||||
|
|
||||||
|
const PartyData = () => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
const variables = { partyId: pubKey || '' };
|
||||||
|
const skip = !pubKey;
|
||||||
|
useDataProvider({
|
||||||
|
dataProvider: activeOrdersProvider,
|
||||||
|
variables,
|
||||||
|
skip,
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const MaybeConnectEagerly = () => {
|
const MaybeConnectEagerly = () => {
|
||||||
useVegaEagerConnect(Connectors);
|
useVegaEagerConnect(Connectors);
|
||||||
useEthereumEagerConnect();
|
useEthereumEagerConnect();
|
||||||
|
@ -8,7 +8,7 @@ const commonProps = {
|
|||||||
|
|
||||||
describe('DateRangeFilter', () => {
|
describe('DateRangeFilter', () => {
|
||||||
it('should be properly rendered', async () => {
|
it('should be properly rendered', async () => {
|
||||||
const defaultRangeFilter = {
|
const defaultValue = {
|
||||||
start: '2023-02-14T13:53:01+01:00',
|
start: '2023-02-14T13:53:01+01:00',
|
||||||
end: '2023-02-21T13:53:01+01:00',
|
end: '2023-02-21T13:53:01+01:00',
|
||||||
};
|
};
|
||||||
@ -17,7 +17,7 @@ describe('DateRangeFilter', () => {
|
|||||||
render(
|
render(
|
||||||
<DateRangeFilter
|
<DateRangeFilter
|
||||||
{...(commonProps as unknown as DateRangeFilterProps)}
|
{...(commonProps as unknown as DateRangeFilterProps)}
|
||||||
defaultRangeFilter={defaultRangeFilter}
|
defaultValue={defaultValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||||
@ -17,9 +17,9 @@ import { formatForInput } from '@vegaprotocol/utils';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
const defaultFilterValue: Schema.DateRange = {};
|
const defaultValue: Schema.DateRange = {};
|
||||||
export interface DateRangeFilterProps extends IFilterParams {
|
export interface DateRangeFilterProps extends IFilterParams {
|
||||||
defaultRangeFilter?: Schema.DateRange;
|
defaultValue?: Schema.DateRange;
|
||||||
maxSubDays?: number;
|
maxSubDays?: number;
|
||||||
maxNextDays?: number;
|
maxNextDays?: number;
|
||||||
maxDaysRange?: number;
|
maxDaysRange?: number;
|
||||||
@ -27,8 +27,9 @@ export interface DateRangeFilterProps extends IFilterParams {
|
|||||||
|
|
||||||
export const DateRangeFilter = forwardRef(
|
export const DateRangeFilter = forwardRef(
|
||||||
(props: DateRangeFilterProps, ref) => {
|
(props: DateRangeFilterProps, ref) => {
|
||||||
const defaultDates = props?.defaultRangeFilter || defaultFilterValue;
|
const defaultDates = props?.defaultValue || defaultValue;
|
||||||
const [value, setValue] = useState<Schema.DateRange>(defaultDates);
|
const [value, setValue] = useState<Schema.DateRange>(defaultDates);
|
||||||
|
const valueRef = useRef<Schema.DateRange>(value);
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
const [minStartDate, maxStartDate, minEndDate, maxEndDate] = useMemo(() => {
|
const [minStartDate, maxStartDate, minEndDate, maxEndDate] = useMemo(() => {
|
||||||
const minStartDate =
|
const minStartDate =
|
||||||
@ -93,7 +94,7 @@ export const DateRangeFilter = forwardRef(
|
|||||||
},
|
},
|
||||||
|
|
||||||
isFilterActive() {
|
isFilterActive() {
|
||||||
return value.start || value.end;
|
return valueRef.current.start || valueRef.current.end;
|
||||||
},
|
},
|
||||||
|
|
||||||
getModel() {
|
getModel() {
|
||||||
@ -101,13 +102,13 @@ export const DateRangeFilter = forwardRef(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { value };
|
return { value: valueRef.current };
|
||||||
},
|
},
|
||||||
|
|
||||||
setModel(model?: { value: Schema.DateRange } | null) {
|
setModel(model?: { value: Schema.DateRange } | null) {
|
||||||
setValue(
|
valueRef.current =
|
||||||
model?.value || props?.defaultRangeFilter || defaultFilterValue
|
model?.value || props?.defaultValue || defaultValue;
|
||||||
);
|
setValue(valueRef.current);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -185,10 +186,8 @@ export const DateRangeFilter = forwardRef(
|
|||||||
update = { ...update, end: checkForEndDate(endDate, startDate) };
|
update = { ...update, end: checkForEndDate(endDate, startDate) };
|
||||||
|
|
||||||
if (validate(name, date, update)) {
|
if (validate(name, date, update)) {
|
||||||
setValue((curr) => ({
|
valueRef.current = { ...valueRef.current, ...update };
|
||||||
...curr,
|
setValue(valueRef.current);
|
||||||
...update,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -241,7 +240,8 @@ export const DateRangeFilter = forwardRef(
|
|||||||
className="ag-standard-button ag-filter-apply-panel-button"
|
className="ag-standard-button ag-filter-apply-panel-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setError('');
|
setError('');
|
||||||
setValue(defaultDates);
|
valueRef.current = defaultDates;
|
||||||
|
setValue(valueRef.current);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Reset')}
|
{t('Reset')}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||||
const [value, setValue] = useState<string[]>([]);
|
const [value, setValue] = useState<string[]>([]);
|
||||||
|
const valueRef = useRef(value);
|
||||||
|
|
||||||
// expose AG Grid Filter Lifecycle callbacks
|
// expose AG Grid Filter Lifecycle callbacks
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
@ -28,29 +35,28 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isFilterActive() {
|
isFilterActive() {
|
||||||
return value.length !== 0;
|
return valueRef.current.length !== 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
getModel() {
|
getModel() {
|
||||||
if (!this.isFilterActive()) {
|
if (!this.isFilterActive()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return { value: valueRef.current };
|
||||||
return { value };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setModel(model?: { value: string[] } | null) {
|
setModel(model?: { value: string[] } | null) {
|
||||||
setValue(!model ? [] : model.value);
|
valueRef.current = !model ? [] : model.value;
|
||||||
|
setValue(valueRef.current);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setValue(
|
valueRef.current = event.target.checked
|
||||||
event.target.checked
|
? [...value, event.target.value]
|
||||||
? [...value, event.target.value]
|
: value.filter((v) => v !== event.target.value);
|
||||||
: value.filter((v) => v !== event.target.value)
|
setValue(valueRef.current);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -77,7 +83,7 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="ag-standard-button ag-filter-apply-panel-button"
|
className="ag-standard-button ag-filter-apply-panel-button"
|
||||||
onClick={() => setValue([])}
|
onClick={() => setValue((valueRef.current = []))}
|
||||||
>
|
>
|
||||||
{t('Reset')}
|
{t('Reset')}
|
||||||
</button>
|
</button>
|
||||||
|
@ -15,8 +15,8 @@ export const useInitialMargin = (
|
|||||||
marketId: OrderSubmissionBody['orderSubmission']['marketId'],
|
marketId: OrderSubmissionBody['orderSubmission']['marketId'],
|
||||||
order?: OrderSubmissionBody['orderSubmission']
|
order?: OrderSubmissionBody['orderSubmission']
|
||||||
) => {
|
) => {
|
||||||
const { pubKey: partyId } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const commonVariables = { marketId, partyId: partyId || '' };
|
const commonVariables = { marketId, partyId: pubKey || '' };
|
||||||
const { data: marketData } = useDataProvider({
|
const { data: marketData } = useDataProvider({
|
||||||
dataProvider: marketDataProvider,
|
dataProvider: marketDataProvider,
|
||||||
variables: { marketId },
|
variables: { marketId },
|
||||||
@ -24,7 +24,7 @@ export const useInitialMargin = (
|
|||||||
const { data: activeVolumeAndMargin } = useDataProvider({
|
const { data: activeVolumeAndMargin } = useDataProvider({
|
||||||
dataProvider: volumeAndMarginProvider,
|
dataProvider: volumeAndMarginProvider,
|
||||||
variables: commonVariables,
|
variables: commonVariables,
|
||||||
skip: !partyId,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
const { data: marketInfo } = useDataProvider({
|
const { data: marketInfo } = useDataProvider({
|
||||||
dataProvider: marketInfoProvider,
|
dataProvider: marketInfoProvider,
|
||||||
|
@ -38,10 +38,10 @@ export const TransferTooltipCellComponent = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRangeFilter = { start: formatRFC3339(subDays(Date.now(), 7)) };
|
const defaultValue = { start: formatRFC3339(subDays(Date.now(), 7)) };
|
||||||
const dateRangeFilterParams = {
|
const dateRangeFilterParams = {
|
||||||
maxNextDays: 0,
|
maxNextDays: 0,
|
||||||
defaultRangeFilter,
|
defaultValue,
|
||||||
};
|
};
|
||||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ export const update = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ordersProvider = makeDataProvider<
|
const ordersProvider = makeDataProvider<
|
||||||
OrdersQuery,
|
OrdersQuery,
|
||||||
ReturnType<typeof getData>,
|
ReturnType<typeof getData>,
|
||||||
OrdersUpdateSubscription,
|
OrdersUpdateSubscription,
|
||||||
@ -165,11 +165,36 @@ export const ordersProvider = makeDataProvider<
|
|||||||
pagination: {
|
pagination: {
|
||||||
getPageInfo,
|
getPageInfo,
|
||||||
append,
|
append,
|
||||||
first: 100,
|
first: 1000,
|
||||||
},
|
},
|
||||||
additionalContext: { isEnlargedTimeout: true },
|
additionalContext: { isEnlargedTimeout: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||||
|
ReturnType<typeof getData>,
|
||||||
|
never,
|
||||||
|
{ partyId: string; marketId?: string }
|
||||||
|
>(
|
||||||
|
[
|
||||||
|
(callback, client, variables) =>
|
||||||
|
ordersProvider(callback, client, {
|
||||||
|
partyId: variables.partyId,
|
||||||
|
filter: {
|
||||||
|
status: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
(partsData, variables, prevData, parts, subscriptions) => {
|
||||||
|
if (!parts[0].isUpdate && subscriptions && subscriptions[0].load) {
|
||||||
|
subscriptions[0].load();
|
||||||
|
}
|
||||||
|
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||||
|
return variables.marketId
|
||||||
|
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||||
|
: orders;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||||
(OrderEdge | null)[],
|
(OrderEdge | null)[],
|
||||||
Order[],
|
Order[],
|
||||||
@ -193,63 +218,20 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
|||||||
combineInsertionData<Order>
|
combineInsertionData<Order>
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasActiveOrderProviderInternal = makeDataProvider<
|
|
||||||
OrdersQuery,
|
|
||||||
boolean,
|
|
||||||
OrdersUpdateSubscription,
|
|
||||||
ReturnType<typeof getDelta>,
|
|
||||||
OrdersQueryVariables
|
|
||||||
>({
|
|
||||||
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<
|
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||||
boolean,
|
boolean,
|
||||||
never,
|
never,
|
||||||
{ partyId: string; marketId?: string }
|
{ partyId: string; marketId?: string }
|
||||||
>(
|
>([activeOrdersProvider], (parts) => !!parts[0].length);
|
||||||
[
|
|
||||||
(callback, client, { partyId, marketId }) =>
|
export const hasAmendableOrderProvider = makeDerivedDataProvider<
|
||||||
hasActiveOrderProviderInternal(callback, client, {
|
boolean,
|
||||||
marketIds: marketId ? [marketId] : undefined,
|
never,
|
||||||
filter: {
|
{ partyId: string; marketId?: string }
|
||||||
status: [OrderStatus.STATUS_ACTIVE],
|
>([activeOrdersProvider], (parts) => {
|
||||||
excludeLiquidity: true,
|
const activeOrders = parts[0] as ReturnType<typeof getData>;
|
||||||
},
|
const hasAmendableOrder = activeOrders.some(
|
||||||
pagination: {
|
(edge) => !(edge.node.liquidityProvision || edge.node.peggedOrder)
|
||||||
first: 1,
|
);
|
||||||
},
|
return hasAmendableOrder;
|
||||||
partyId,
|
});
|
||||||
} as OrdersQueryVariables),
|
|
||||||
],
|
|
||||||
(parts) => parts[0]
|
|
||||||
);
|
|
||||||
|
@ -8,7 +8,7 @@ import type { GridReadyEvent } from 'ag-grid-community';
|
|||||||
|
|
||||||
import { OrderListTable } from '../order-list/order-list';
|
import { OrderListTable } from '../order-list/order-list';
|
||||||
import { useOrderListData } from './use-order-list-data';
|
import { useOrderListData } from './use-order-list-data';
|
||||||
import { useHasActiveOrder } from '../../order-hooks/use-has-active-order';
|
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||||
import type { Filter, Sort } from './use-order-list-data';
|
import type { Filter, Sort } from './use-order-list-data';
|
||||||
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { OrderStatus } from '@vegaprotocol/types';
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
normalizeOrderAmendment,
|
normalizeOrderAmendment,
|
||||||
useVegaTransactionStore,
|
useVegaTransactionStore,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||||
import type { Order, OrderEdge } from '../order-data-provider';
|
import type { Order, OrderEdge } from '../order-data-provider';
|
||||||
@ -28,27 +29,18 @@ export interface OrderListManagerProps {
|
|||||||
enforceBottomPlaceholder?: boolean;
|
enforceBottomPlaceholder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CancelAllOrdersButton = ({
|
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||||
onClick,
|
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
||||||
marketId,
|
<Button
|
||||||
}: {
|
variant="primary"
|
||||||
onClick: (marketId?: string) => void;
|
size="sm"
|
||||||
marketId?: string;
|
onClick={onClick}
|
||||||
}) => {
|
data-testid="cancelAll"
|
||||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
>
|
||||||
return hasActiveOrder ? (
|
{t('Cancel all')}
|
||||||
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
</Button>
|
||||||
<Button
|
</div>
|
||||||
variant="primary"
|
);
|
||||||
size="sm"
|
|
||||||
onClick={() => onClick(marketId)}
|
|
||||||
data-testid="cancelAll"
|
|
||||||
>
|
|
||||||
{t('Cancel all')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialFilter: Filter = {
|
const initialFilter: Filter = {
|
||||||
status: {
|
status: {
|
||||||
@ -68,9 +60,10 @@ export const OrderListManager = ({
|
|||||||
const scrolledToTop = useRef(false);
|
const scrolledToTop = useRef(false);
|
||||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||||
const [filter, setFilter] = useState<Filter | undefined>(initialFilter);
|
const [filter, setFilter] = useState<Filter | undefined>(initialFilter);
|
||||||
|
const filterRef = useRef(initialFilter);
|
||||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||||
const create = useVegaTransactionStore((state) => state.create);
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||||
|
|
||||||
const { data, error, loading, reload } = useOrderListData({
|
const { data, error, loading, reload } = useOrderListData({
|
||||||
partyId,
|
partyId,
|
||||||
@ -86,12 +79,16 @@ export const OrderListManager = ({
|
|||||||
...bottomPlaceholderProps
|
...bottomPlaceholderProps
|
||||||
} = useBottomPlaceholder<Order>({
|
} = useBottomPlaceholder<Order>({
|
||||||
gridRef,
|
gridRef,
|
||||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasActiveOrder,
|
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFilterChanged = useCallback(
|
const onFilterChanged = useCallback(
|
||||||
(event: FilterChangedEvent) => {
|
(event: FilterChangedEvent) => {
|
||||||
const updatedFilter = event.api.getFilterModel();
|
const updatedFilter = event.api.getFilterModel();
|
||||||
|
if (isEqual(updatedFilter, filterRef.current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filterRef.current = updatedFilter;
|
||||||
if (Object.keys(updatedFilter).length) {
|
if (Object.keys(updatedFilter).length) {
|
||||||
setFilter(updatedFilter);
|
setFilter(updatedFilter);
|
||||||
} else {
|
} else {
|
||||||
@ -142,16 +139,13 @@ export const OrderListManager = ({
|
|||||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const cancelAll = useCallback(
|
const cancelAll = useCallback(() => {
|
||||||
(marketId?: string) => {
|
create({
|
||||||
create({
|
orderCancellation: {
|
||||||
orderCancellation: {
|
marketId,
|
||||||
marketId,
|
},
|
||||||
},
|
});
|
||||||
});
|
}, [create, marketId]);
|
||||||
},
|
|
||||||
[create]
|
|
||||||
);
|
|
||||||
const extractedData =
|
const extractedData =
|
||||||
data && !loading
|
data && !loading
|
||||||
? data
|
? data
|
||||||
@ -188,8 +182,8 @@ export const OrderListManager = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && hasAmendableOrder && (
|
||||||
<CancelAllOrdersButton onClick={cancelAll} marketId={marketId} />
|
<CancelAllOrdersButton onClick={cancelAll} />
|
||||||
)}
|
)}
|
||||||
{editOrder && (
|
{editOrder && (
|
||||||
<OrderEditDialog
|
<OrderEditDialog
|
||||||
|
@ -71,17 +71,27 @@ export const useOrderListData = ({
|
|||||||
// define variable as const to get type safety, using generic with useMemo resulted in lost type safety
|
// define variable as const to get type safety, using generic with useMemo resulted in lost type safety
|
||||||
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
|
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
|
||||||
partyId,
|
partyId,
|
||||||
filter: {
|
|
||||||
dateRange: filter?.updatedAt?.value,
|
|
||||||
status: filter?.status?.value,
|
|
||||||
timeInForce: filter?.timeInForce?.value,
|
|
||||||
types: filter?.type?.value,
|
|
||||||
},
|
|
||||||
pagination: {
|
|
||||||
first: 1000,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
if (
|
||||||
|
filter?.updatedAt?.value ||
|
||||||
|
filter?.status?.value.length ||
|
||||||
|
filter?.timeInForce?.value.length ||
|
||||||
|
filter?.type?.value.length
|
||||||
|
) {
|
||||||
|
allVars.filter = {};
|
||||||
|
if (filter?.updatedAt?.value) {
|
||||||
|
allVars.filter.dateRange = filter?.updatedAt?.value;
|
||||||
|
}
|
||||||
|
if (filter?.status?.value.length) {
|
||||||
|
allVars.filter.status = filter?.status?.value;
|
||||||
|
}
|
||||||
|
if (filter?.timeInForce?.value.length) {
|
||||||
|
allVars.filter.timeInForce = filter?.timeInForce?.value;
|
||||||
|
}
|
||||||
|
if (filter?.type?.value.length) {
|
||||||
|
allVars.filter.types = filter?.type?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
return allVars;
|
return allVars;
|
||||||
}, [partyId, filter]);
|
}, [partyId, filter]);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
export * from './__generated__/OrdersSubscription';
|
export * from './__generated__/OrdersSubscription';
|
||||||
export * from './use-has-active-order';
|
export * from './use-has-amendable-order';
|
||||||
export * from './use-order-update';
|
export * from './use-order-update';
|
||||||
export * from './use-pending-orders-volume';
|
|
||||||
export * from './use-order-store';
|
export * from './use-order-store';
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { hasActiveOrderProvider } from '../components/order-data-provider/';
|
import { hasAmendableOrderProvider } from '../components/order-data-provider';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export const useHasActiveOrder = (marketId?: string) => {
|
export const useHasAmendableOrder = (marketId?: string) => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const [hasActiveOrder, setHasActiveOrder] = useState(false);
|
const [hasAmendableOrder, setHasAmendableOrder] = useState(false);
|
||||||
const update = useCallback(({ data }: { data: boolean | null }) => {
|
const update = useCallback(({ data }: { data: boolean | null }) => {
|
||||||
setHasActiveOrder(Boolean(data));
|
setHasAmendableOrder(Boolean(data));
|
||||||
return true;
|
return true;
|
||||||
}, []);
|
}, []);
|
||||||
useDataProvider({
|
useDataProvider({
|
||||||
dataProvider: hasActiveOrderProvider,
|
dataProvider: hasAmendableOrderProvider,
|
||||||
update,
|
update,
|
||||||
variables: {
|
variables: {
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
@ -20,5 +20,5 @@ export const useHasActiveOrder = (marketId?: string) => {
|
|||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasActiveOrder;
|
return hasAmendableOrder;
|
||||||
};
|
};
|
@ -1,74 +0,0 @@
|
|||||||
import { useState, useCallback } from 'react';
|
|
||||||
import { OrderStatus, Side } from '@vegaprotocol/types';
|
|
||||||
import { ordersProvider } from '../components/order-data-provider/order-data-provider';
|
|
||||||
import type { OrderFieldsFragment } from '../components/order-data-provider/__generated__/Orders';
|
|
||||||
import type { Edge } from '@vegaprotocol/utils';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
const sumVolume = (orders: (Edge<OrderFieldsFragment> | null)[], side: Side) =>
|
|
||||||
orders
|
|
||||||
.reduce(
|
|
||||||
(sum, order) =>
|
|
||||||
order?.node.side === side
|
|
||||||
? sum +
|
|
||||||
BigInt(
|
|
||||||
order?.node.status === OrderStatus.STATUS_PARTIALLY_FILLED
|
|
||||||
? order?.node.remaining
|
|
||||||
: order?.node.size
|
|
||||||
)
|
|
||||||
: sum,
|
|
||||||
BigInt(0)
|
|
||||||
)
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
export const useActiveOrdersVolumeAndMargin = (
|
|
||||||
partyId: string | null | undefined,
|
|
||||||
marketId: string
|
|
||||||
) => {
|
|
||||||
const [buyVolume, setBuyVolume] = useState<string | undefined>();
|
|
||||||
const [sellVolume, setSellVolume] = useState<string | undefined>();
|
|
||||||
const [buyInitialMargin, setBuyInitialMargin] = useState<
|
|
||||||
string | undefined
|
|
||||||
>();
|
|
||||||
const [sellInitialMargin, setSellInitialMargin] = useState<
|
|
||||||
string | undefined
|
|
||||||
>();
|
|
||||||
const update = useCallback(
|
|
||||||
({ data }: { data: (Edge<OrderFieldsFragment> | null)[] | null }) => {
|
|
||||||
if (!data) {
|
|
||||||
setBuyVolume(undefined);
|
|
||||||
setSellVolume(undefined);
|
|
||||||
setBuyInitialMargin(undefined);
|
|
||||||
setSellInitialMargin(undefined);
|
|
||||||
} else {
|
|
||||||
setBuyVolume(sumVolume(data, Side.SIDE_BUY));
|
|
||||||
setSellVolume(sumVolume(data, Side.SIDE_SELL));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
useDataProvider({
|
|
||||||
dataProvider: ordersProvider,
|
|
||||||
update,
|
|
||||||
variables: {
|
|
||||||
partyId: partyId || '',
|
|
||||||
marketIds: [marketId],
|
|
||||||
filter: {
|
|
||||||
status: [
|
|
||||||
OrderStatus.STATUS_ACTIVE,
|
|
||||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
skip: !partyId,
|
|
||||||
});
|
|
||||||
return buyVolume || sellVolume
|
|
||||||
? {
|
|
||||||
buyVolume,
|
|
||||||
sellVolume,
|
|
||||||
buyInitialMargin,
|
|
||||||
sellInitialMargin,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
};
|
|
@ -30,12 +30,12 @@ import {
|
|||||||
import { marginsDataProvider } from './margin-data-provider';
|
import { marginsDataProvider } from './margin-data-provider';
|
||||||
import { calculateMargins } from './margin-calculator';
|
import { calculateMargins } from './margin-calculator';
|
||||||
import type { Edge } from '@vegaprotocol/utils';
|
import type { Edge } from '@vegaprotocol/utils';
|
||||||
import { OrderStatus, Side } from '@vegaprotocol/types';
|
import { Side } from '@vegaprotocol/types';
|
||||||
import { marketInfoProvider } from '@vegaprotocol/market-info';
|
import { marketInfoProvider } from '@vegaprotocol/market-info';
|
||||||
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
|
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
|
||||||
import { marketDataProvider } from '@vegaprotocol/market-list';
|
import { marketDataProvider } from '@vegaprotocol/market-list';
|
||||||
import type { MarketData } from '@vegaprotocol/market-list';
|
import type { MarketData } from '@vegaprotocol/market-list';
|
||||||
import { ordersProvider } from '@vegaprotocol/orders';
|
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||||
import type { OrderFieldsFragment } from '@vegaprotocol/orders';
|
import type { OrderFieldsFragment } from '@vegaprotocol/orders';
|
||||||
import type { PositionStatus } from '@vegaprotocol/types';
|
import type { PositionStatus } from '@vegaprotocol/types';
|
||||||
|
|
||||||
@ -350,12 +350,9 @@ export const volumeAndMarginProvider = makeDerivedDataProvider<
|
|||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
(callback, client, { partyId, marketId }) =>
|
(callback, client, { partyId, marketId }) =>
|
||||||
ordersProvider(callback, client, {
|
activeOrdersProvider(callback, client, {
|
||||||
partyId,
|
partyId,
|
||||||
marketIds: [marketId],
|
marketId,
|
||||||
filter: {
|
|
||||||
status: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
(callback, client, variables) =>
|
(callback, client, variables) =>
|
||||||
marketDataProvider(callback, client, { marketId: variables.marketId }),
|
marketDataProvider(callback, client, { marketId: variables.marketId }),
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqualWith from 'lodash/isEqualWith';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { usePrevious } from './use-previous';
|
|
||||||
import type { OperationVariables } from '@apollo/client';
|
import type { OperationVariables } from '@apollo/client';
|
||||||
import type { Subscribe, Load, UpdateCallback } from '@vegaprotocol/utils';
|
import type { Subscribe, Load, UpdateCallback } from '@vegaprotocol/utils';
|
||||||
|
import { variablesIsEqualCustomizer } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
export interface useDataProviderParams<
|
export interface useDataProviderParams<
|
||||||
Data,
|
Data,
|
||||||
@ -62,13 +62,19 @@ export const useDataProvider = <
|
|||||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||||
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
||||||
const loadRef = useRef<Load<Data> | undefined>(undefined);
|
const loadRef = useRef<Load<Data> | undefined>(undefined);
|
||||||
const prevVariables = usePrevious(props.variables);
|
const variablesRef = useRef<Variables>(props.variables);
|
||||||
const [variables, setVariables] = useState(props.variables);
|
const variables = useMemo(() => {
|
||||||
useEffect(() => {
|
if (
|
||||||
if (!isEqual(prevVariables, props.variables)) {
|
!isEqualWith(
|
||||||
setVariables(props.variables);
|
variablesRef.current,
|
||||||
|
props.variables,
|
||||||
|
variablesIsEqualCustomizer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
variablesRef.current = props.variables;
|
||||||
}
|
}
|
||||||
}, [props.variables, prevVariables]);
|
return variablesRef.current;
|
||||||
|
}, [props.variables]);
|
||||||
const flush = useCallback(() => {
|
const flush = useCallback(() => {
|
||||||
if (flushRef.current) {
|
if (flushRef.current) {
|
||||||
flushRef.current();
|
flushRef.current();
|
||||||
|
@ -45,7 +45,7 @@ type CombinedData = {
|
|||||||
|
|
||||||
type SubscriptionData = QueryData;
|
type SubscriptionData = QueryData;
|
||||||
type Delta = Data;
|
type Delta = Data;
|
||||||
type Variables = { var: string };
|
type Variables = { var: string; filter?: string[] };
|
||||||
|
|
||||||
const update = jest.fn<
|
const update = jest.fn<
|
||||||
ReturnType<Update<Data, Delta, Variables>>,
|
ReturnType<Update<Data, Delta, Variables>>,
|
||||||
@ -231,10 +231,14 @@ describe('data provider', () => {
|
|||||||
clientSubscribeSubscribe.mockClear();
|
clientSubscribeSubscribe.mockClear();
|
||||||
});
|
});
|
||||||
it('memoize instance and unsubscribe if no subscribers', () => {
|
it('memoize instance and unsubscribe if no subscribers', () => {
|
||||||
const subscription1 = subscribe(jest.fn(), client, variables);
|
const subscription1 = subscribe(jest.fn(), client, {
|
||||||
const subscription2 = subscribe(jest.fn(), client, { ...variables });
|
...variables,
|
||||||
// const subscription1 = subscribe(jest.fn(), client);
|
filter: ['1', '2'],
|
||||||
// const subscription2 = subscribe(jest.fn(), client);
|
});
|
||||||
|
const subscription2 = subscribe(jest.fn(), client, {
|
||||||
|
...variables,
|
||||||
|
filter: ['2', '1'],
|
||||||
|
});
|
||||||
expect(clientSubscribeSubscribe.mock.calls.length).toEqual(1);
|
expect(clientSubscribeSubscribe.mock.calls.length).toEqual(1);
|
||||||
subscription1.unsubscribe();
|
subscription1.unsubscribe();
|
||||||
expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(0);
|
expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(0);
|
||||||
|
@ -10,7 +10,7 @@ import type {
|
|||||||
} from '@apollo/client';
|
} from '@apollo/client';
|
||||||
import type { GraphQLErrors } from '@apollo/client/errors';
|
import type { GraphQLErrors } from '@apollo/client/errors';
|
||||||
import type { Subscription } from 'zen-observable-ts';
|
import type { Subscription } from 'zen-observable-ts';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqualWith from 'lodash/isEqualWith';
|
||||||
import { isNotFoundGraphQLError } from './apollo-client';
|
import { isNotFoundGraphQLError } from './apollo-client';
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
interface UpdateData<Data, Delta> {
|
interface UpdateData<Data, Delta> {
|
||||||
@ -512,11 +512,26 @@ function makeDataProviderInternal<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two arrays assuming that they are sets of primitive values, used to compare gql query variables
|
||||||
|
*/
|
||||||
|
export const variablesIsEqualCustomizer: NonNullable<
|
||||||
|
Parameters<typeof isEqualWith>['2']
|
||||||
|
> = (value, other) => {
|
||||||
|
if (Array.isArray(value) && Array.isArray(other)) {
|
||||||
|
return (
|
||||||
|
value.length === other.length &&
|
||||||
|
new Set([...value, ...other]).size === value.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memoizes data provider instances using query variables as cache key
|
* Memoizes data provider instances using query variables as cache key
|
||||||
*
|
*
|
||||||
* @param fn
|
* @param fn
|
||||||
* @returns subscibe function
|
* @returns subscribe function
|
||||||
*/
|
*/
|
||||||
const memoize = <
|
const memoize = <
|
||||||
Data,
|
Data,
|
||||||
@ -530,7 +545,9 @@ const memoize = <
|
|||||||
variables?: Variables;
|
variables?: Variables;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
return (variables?: Variables) => {
|
return (variables?: Variables) => {
|
||||||
const cached = cache.find((c) => isEqual(c.variables, variables));
|
const cached = cache.find((c) =>
|
||||||
|
isEqualWith(c.variables, variables, variablesIsEqualCustomizer)
|
||||||
|
);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached.subscribe;
|
return cached.subscribe;
|
||||||
}
|
}
|
||||||
@ -582,8 +599,10 @@ export function makeDataProvider<
|
|||||||
const getInstance = memoize<Data, Delta, Variables>(() =>
|
const getInstance = memoize<Data, Delta, Variables>(() =>
|
||||||
makeDataProviderInternal(params)
|
makeDataProviderInternal(params)
|
||||||
);
|
);
|
||||||
return (callback, client, variables) =>
|
return (callback, client, variables) => {
|
||||||
getInstance(variables)(callback, client, variables);
|
const instance = getInstance(variables)(callback, client, variables);
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -605,7 +624,9 @@ export type CombineDerivedData<
|
|||||||
> = (
|
> = (
|
||||||
data: DerivedPart<Variables>['data'][],
|
data: DerivedPart<Variables>['data'][],
|
||||||
variables: Variables,
|
variables: Variables,
|
||||||
prevData: Data | null
|
prevData: Data | null,
|
||||||
|
parts: DerivedPart<Variables>[],
|
||||||
|
subscriptions?: ReturnType<DependencySubscribe<Variables>>[]
|
||||||
) => Data | null;
|
) => Data | null;
|
||||||
|
|
||||||
export type CombineDerivedDelta<
|
export type CombineDerivedDelta<
|
||||||
@ -687,7 +708,9 @@ function makeDerivedDataProviderInternal<
|
|||||||
? combineData(
|
? combineData(
|
||||||
parts.map((part) => part.data),
|
parts.map((part) => part.data),
|
||||||
variables,
|
variables,
|
||||||
data
|
data,
|
||||||
|
parts,
|
||||||
|
subscriptions
|
||||||
)
|
)
|
||||||
: data;
|
: data;
|
||||||
if (
|
if (
|
||||||
|
Loading…
Reference in New Issue
Block a user