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 { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@ -95,6 +97,7 @@ function AppBody({ Component }: AppProps) {
|
||||
<ToastsManager />
|
||||
<InitializeHandlers />
|
||||
<MaybeConnectEagerly />
|
||||
<PartyData />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -127,6 +130,18 @@ function VegaTradingApp(props: AppProps) {
|
||||
|
||||
export default VegaTradingApp;
|
||||
|
||||
const PartyData = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const variables = { partyId: pubKey || '' };
|
||||
const skip = !pubKey;
|
||||
useDataProvider({
|
||||
dataProvider: activeOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
useVegaEagerConnect(Connectors);
|
||||
useEthereumEagerConnect();
|
||||
|
@ -8,7 +8,7 @@ const commonProps = {
|
||||
|
||||
describe('DateRangeFilter', () => {
|
||||
it('should be properly rendered', async () => {
|
||||
const defaultRangeFilter = {
|
||||
const defaultValue = {
|
||||
start: '2023-02-14T13:53:01+01:00',
|
||||
end: '2023-02-21T13:53:01+01:00',
|
||||
};
|
||||
@ -17,7 +17,7 @@ describe('DateRangeFilter', () => {
|
||||
render(
|
||||
<DateRangeFilter
|
||||
{...(commonProps as unknown as DateRangeFilterProps)}
|
||||
defaultRangeFilter={defaultRangeFilter}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||
@ -17,9 +17,9 @@ import { formatForInput } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const defaultFilterValue: Schema.DateRange = {};
|
||||
const defaultValue: Schema.DateRange = {};
|
||||
export interface DateRangeFilterProps extends IFilterParams {
|
||||
defaultRangeFilter?: Schema.DateRange;
|
||||
defaultValue?: Schema.DateRange;
|
||||
maxSubDays?: number;
|
||||
maxNextDays?: number;
|
||||
maxDaysRange?: number;
|
||||
@ -27,8 +27,9 @@ export interface DateRangeFilterProps extends IFilterParams {
|
||||
|
||||
export const DateRangeFilter = forwardRef(
|
||||
(props: DateRangeFilterProps, ref) => {
|
||||
const defaultDates = props?.defaultRangeFilter || defaultFilterValue;
|
||||
const defaultDates = props?.defaultValue || defaultValue;
|
||||
const [value, setValue] = useState<Schema.DateRange>(defaultDates);
|
||||
const valueRef = useRef<Schema.DateRange>(value);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [minStartDate, maxStartDate, minEndDate, maxEndDate] = useMemo(() => {
|
||||
const minStartDate =
|
||||
@ -93,7 +94,7 @@ export const DateRangeFilter = forwardRef(
|
||||
},
|
||||
|
||||
isFilterActive() {
|
||||
return value.start || value.end;
|
||||
return valueRef.current.start || valueRef.current.end;
|
||||
},
|
||||
|
||||
getModel() {
|
||||
@ -101,13 +102,13 @@ export const DateRangeFilter = forwardRef(
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value };
|
||||
return { value: valueRef.current };
|
||||
},
|
||||
|
||||
setModel(model?: { value: Schema.DateRange } | null) {
|
||||
setValue(
|
||||
model?.value || props?.defaultRangeFilter || defaultFilterValue
|
||||
);
|
||||
valueRef.current =
|
||||
model?.value || props?.defaultValue || defaultValue;
|
||||
setValue(valueRef.current);
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -185,10 +186,8 @@ export const DateRangeFilter = forwardRef(
|
||||
update = { ...update, end: checkForEndDate(endDate, startDate) };
|
||||
|
||||
if (validate(name, date, update)) {
|
||||
setValue((curr) => ({
|
||||
...curr,
|
||||
...update,
|
||||
}));
|
||||
valueRef.current = { ...valueRef.current, ...update };
|
||||
setValue(valueRef.current);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
@ -241,7 +240,8 @@ export const DateRangeFilter = forwardRef(
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => {
|
||||
setError('');
|
||||
setValue(defaultDates);
|
||||
valueRef.current = defaultDates;
|
||||
setValue(valueRef.current);
|
||||
}}
|
||||
>
|
||||
{t('Reset')}
|
||||
|
@ -1,10 +1,17 @@
|
||||
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 { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
const valueRef = useRef(value);
|
||||
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
@ -28,29 +35,28 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
},
|
||||
|
||||
isFilterActive() {
|
||||
return value.length !== 0;
|
||||
return valueRef.current.length !== 0;
|
||||
},
|
||||
|
||||
getModel() {
|
||||
if (!this.isFilterActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { value };
|
||||
return { value: valueRef.current };
|
||||
},
|
||||
|
||||
setModel(model?: { value: string[] } | null) {
|
||||
setValue(!model ? [] : model.value);
|
||||
valueRef.current = !model ? [] : model.value;
|
||||
setValue(valueRef.current);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(
|
||||
event.target.checked
|
||||
? [...value, event.target.value]
|
||||
: value.filter((v) => v !== event.target.value)
|
||||
);
|
||||
valueRef.current = event.target.checked
|
||||
? [...value, event.target.value]
|
||||
: value.filter((v) => v !== event.target.value);
|
||||
setValue(valueRef.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -77,7 +83,7 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
<button
|
||||
type="button"
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue([])}
|
||||
onClick={() => setValue((valueRef.current = []))}
|
||||
>
|
||||
{t('Reset')}
|
||||
</button>
|
||||
|
@ -15,8 +15,8 @@ export const useInitialMargin = (
|
||||
marketId: OrderSubmissionBody['orderSubmission']['marketId'],
|
||||
order?: OrderSubmissionBody['orderSubmission']
|
||||
) => {
|
||||
const { pubKey: partyId } = useVegaWallet();
|
||||
const commonVariables = { marketId, partyId: partyId || '' };
|
||||
const { pubKey } = useVegaWallet();
|
||||
const commonVariables = { marketId, partyId: pubKey || '' };
|
||||
const { data: marketData } = useDataProvider({
|
||||
dataProvider: marketDataProvider,
|
||||
variables: { marketId },
|
||||
@ -24,7 +24,7 @@ export const useInitialMargin = (
|
||||
const { data: activeVolumeAndMargin } = useDataProvider({
|
||||
dataProvider: volumeAndMarginProvider,
|
||||
variables: commonVariables,
|
||||
skip: !partyId,
|
||||
skip: !pubKey,
|
||||
});
|
||||
const { data: marketInfo } = useDataProvider({
|
||||
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 = {
|
||||
maxNextDays: 0,
|
||||
defaultRangeFilter,
|
||||
defaultValue,
|
||||
};
|
||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||
|
||||
|
@ -150,7 +150,7 @@ export const update = (
|
||||
});
|
||||
};
|
||||
|
||||
export const ordersProvider = makeDataProvider<
|
||||
const ordersProvider = makeDataProvider<
|
||||
OrdersQuery,
|
||||
ReturnType<typeof getData>,
|
||||
OrdersUpdateSubscription,
|
||||
@ -165,11 +165,36 @@ export const ordersProvider = makeDataProvider<
|
||||
pagination: {
|
||||
getPageInfo,
|
||||
append,
|
||||
first: 100,
|
||||
first: 1000,
|
||||
},
|
||||
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<
|
||||
(OrderEdge | null)[],
|
||||
Order[],
|
||||
@ -193,63 +218,20 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
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<
|
||||
boolean,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>(
|
||||
[
|
||||
(callback, client, { partyId, marketId }) =>
|
||||
hasActiveOrderProviderInternal(callback, client, {
|
||||
marketIds: marketId ? [marketId] : undefined,
|
||||
filter: {
|
||||
status: [OrderStatus.STATUS_ACTIVE],
|
||||
excludeLiquidity: true,
|
||||
},
|
||||
pagination: {
|
||||
first: 1,
|
||||
},
|
||||
partyId,
|
||||
} as OrdersQueryVariables),
|
||||
],
|
||||
(parts) => parts[0]
|
||||
);
|
||||
>([activeOrdersProvider], (parts) => !!parts[0].length);
|
||||
|
||||
export const hasAmendableOrderProvider = makeDerivedDataProvider<
|
||||
boolean,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>([activeOrdersProvider], (parts) => {
|
||||
const activeOrders = parts[0] as ReturnType<typeof getData>;
|
||||
const hasAmendableOrder = activeOrders.some(
|
||||
(edge) => !(edge.node.liquidityProvision || edge.node.peggedOrder)
|
||||
);
|
||||
return hasAmendableOrder;
|
||||
});
|
||||
|
@ -8,7 +8,7 @@ import type { GridReadyEvent } from 'ag-grid-community';
|
||||
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
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 { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
@ -16,6 +16,7 @@ import {
|
||||
normalizeOrderAmendment,
|
||||
useVegaTransactionStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order, OrderEdge } from '../order-data-provider';
|
||||
@ -28,27 +29,18 @@ export interface OrderListManagerProps {
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
}
|
||||
|
||||
const CancelAllOrdersButton = ({
|
||||
onClick,
|
||||
marketId,
|
||||
}: {
|
||||
onClick: (marketId?: string) => void;
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||
return hasActiveOrder ? (
|
||||
<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
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => onClick(marketId)}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<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
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const initialFilter: Filter = {
|
||||
status: {
|
||||
@ -68,9 +60,10 @@ export const OrderListManager = ({
|
||||
const scrolledToTop = useRef(false);
|
||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||
const [filter, setFilter] = useState<Filter | undefined>(initialFilter);
|
||||
const filterRef = useRef(initialFilter);
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
|
||||
const { data, error, loading, reload } = useOrderListData({
|
||||
partyId,
|
||||
@ -86,12 +79,16 @@ export const OrderListManager = ({
|
||||
...bottomPlaceholderProps
|
||||
} = useBottomPlaceholder<Order>({
|
||||
gridRef,
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasActiveOrder,
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = event.api.getFilterModel();
|
||||
if (isEqual(updatedFilter, filterRef.current)) {
|
||||
return;
|
||||
}
|
||||
filterRef.current = updatedFilter;
|
||||
if (Object.keys(updatedFilter).length) {
|
||||
setFilter(updatedFilter);
|
||||
} else {
|
||||
@ -142,16 +139,13 @@ export const OrderListManager = ({
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
}, [data]);
|
||||
|
||||
const cancelAll = useCallback(
|
||||
(marketId?: string) => {
|
||||
create({
|
||||
orderCancellation: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
},
|
||||
[create]
|
||||
);
|
||||
const cancelAll = useCallback(() => {
|
||||
create({
|
||||
orderCancellation: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
}, [create, marketId]);
|
||||
const extractedData =
|
||||
data && !loading
|
||||
? data
|
||||
@ -188,8 +182,8 @@ export const OrderListManager = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<CancelAllOrdersButton onClick={cancelAll} marketId={marketId} />
|
||||
{!isReadOnly && hasAmendableOrder && (
|
||||
<CancelAllOrdersButton onClick={cancelAll} />
|
||||
)}
|
||||
{editOrder && (
|
||||
<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
|
||||
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
|
||||
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;
|
||||
}, [partyId, filter]);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './__generated__/OrdersSubscription';
|
||||
export * from './use-has-active-order';
|
||||
export * from './use-has-amendable-order';
|
||||
export * from './use-order-update';
|
||||
export * from './use-pending-orders-volume';
|
||||
export * from './use-order-store';
|
||||
|
@ -1,17 +1,17 @@
|
||||
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 { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export const useHasActiveOrder = (marketId?: string) => {
|
||||
export const useHasAmendableOrder = (marketId?: string) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const [hasActiveOrder, setHasActiveOrder] = useState(false);
|
||||
const [hasAmendableOrder, setHasAmendableOrder] = useState(false);
|
||||
const update = useCallback(({ data }: { data: boolean | null }) => {
|
||||
setHasActiveOrder(Boolean(data));
|
||||
setHasAmendableOrder(Boolean(data));
|
||||
return true;
|
||||
}, []);
|
||||
useDataProvider({
|
||||
dataProvider: hasActiveOrderProvider,
|
||||
dataProvider: hasAmendableOrderProvider,
|
||||
update,
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
@ -20,5 +20,5 @@ export const useHasActiveOrder = (marketId?: string) => {
|
||||
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 { calculateMargins } from './margin-calculator';
|
||||
import type { Edge } from '@vegaprotocol/utils';
|
||||
import { OrderStatus, Side } from '@vegaprotocol/types';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import { marketInfoProvider } from '@vegaprotocol/market-info';
|
||||
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
|
||||
import { marketDataProvider } 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 { PositionStatus } from '@vegaprotocol/types';
|
||||
|
||||
@ -350,12 +350,9 @@ export const volumeAndMarginProvider = makeDerivedDataProvider<
|
||||
>(
|
||||
[
|
||||
(callback, client, { partyId, marketId }) =>
|
||||
ordersProvider(callback, client, {
|
||||
activeOrdersProvider(callback, client, {
|
||||
partyId,
|
||||
marketIds: [marketId],
|
||||
filter: {
|
||||
status: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
},
|
||||
marketId,
|
||||
}),
|
||||
(callback, client, variables) =>
|
||||
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 isEqual from 'lodash/isEqual';
|
||||
import isEqualWith from 'lodash/isEqualWith';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { usePrevious } from './use-previous';
|
||||
import type { OperationVariables } from '@apollo/client';
|
||||
import type { Subscribe, Load, UpdateCallback } from '@vegaprotocol/utils';
|
||||
import { variablesIsEqualCustomizer } from '@vegaprotocol/utils';
|
||||
|
||||
export interface useDataProviderParams<
|
||||
Data,
|
||||
@ -62,13 +62,19 @@ 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 prevVariables = usePrevious(props.variables);
|
||||
const [variables, setVariables] = useState(props.variables);
|
||||
useEffect(() => {
|
||||
if (!isEqual(prevVariables, props.variables)) {
|
||||
setVariables(props.variables);
|
||||
const variablesRef = useRef<Variables>(props.variables);
|
||||
const variables = useMemo(() => {
|
||||
if (
|
||||
!isEqualWith(
|
||||
variablesRef.current,
|
||||
props.variables,
|
||||
variablesIsEqualCustomizer
|
||||
)
|
||||
) {
|
||||
variablesRef.current = props.variables;
|
||||
}
|
||||
}, [props.variables, prevVariables]);
|
||||
return variablesRef.current;
|
||||
}, [props.variables]);
|
||||
const flush = useCallback(() => {
|
||||
if (flushRef.current) {
|
||||
flushRef.current();
|
||||
|
@ -45,7 +45,7 @@ type CombinedData = {
|
||||
|
||||
type SubscriptionData = QueryData;
|
||||
type Delta = Data;
|
||||
type Variables = { var: string };
|
||||
type Variables = { var: string; filter?: string[] };
|
||||
|
||||
const update = jest.fn<
|
||||
ReturnType<Update<Data, Delta, Variables>>,
|
||||
@ -231,10 +231,14 @@ describe('data provider', () => {
|
||||
clientSubscribeSubscribe.mockClear();
|
||||
});
|
||||
it('memoize instance and unsubscribe if no subscribers', () => {
|
||||
const subscription1 = subscribe(jest.fn(), client, variables);
|
||||
const subscription2 = subscribe(jest.fn(), client, { ...variables });
|
||||
// const subscription1 = subscribe(jest.fn(), client);
|
||||
// const subscription2 = subscribe(jest.fn(), client);
|
||||
const subscription1 = subscribe(jest.fn(), client, {
|
||||
...variables,
|
||||
filter: ['1', '2'],
|
||||
});
|
||||
const subscription2 = subscribe(jest.fn(), client, {
|
||||
...variables,
|
||||
filter: ['2', '1'],
|
||||
});
|
||||
expect(clientSubscribeSubscribe.mock.calls.length).toEqual(1);
|
||||
subscription1.unsubscribe();
|
||||
expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(0);
|
||||
|
@ -10,7 +10,7 @@ import type {
|
||||
} from '@apollo/client';
|
||||
import type { GraphQLErrors } from '@apollo/client/errors';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import isEqualWith from 'lodash/isEqualWith';
|
||||
import { isNotFoundGraphQLError } from './apollo-client';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
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
|
||||
*
|
||||
* @param fn
|
||||
* @returns subscibe function
|
||||
* @returns subscribe function
|
||||
*/
|
||||
const memoize = <
|
||||
Data,
|
||||
@ -530,7 +545,9 @@ const memoize = <
|
||||
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) {
|
||||
return cached.subscribe;
|
||||
}
|
||||
@ -582,8 +599,10 @@ export function makeDataProvider<
|
||||
const getInstance = memoize<Data, Delta, Variables>(() =>
|
||||
makeDataProviderInternal(params)
|
||||
);
|
||||
return (callback, client, variables) =>
|
||||
getInstance(variables)(callback, client, variables);
|
||||
return (callback, client, variables) => {
|
||||
const instance = getInstance(variables)(callback, client, variables);
|
||||
return instance;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -605,7 +624,9 @@ export type CombineDerivedData<
|
||||
> = (
|
||||
data: DerivedPart<Variables>['data'][],
|
||||
variables: Variables,
|
||||
prevData: Data | null
|
||||
prevData: Data | null,
|
||||
parts: DerivedPart<Variables>[],
|
||||
subscriptions?: ReturnType<DependencySubscribe<Variables>>[]
|
||||
) => Data | null;
|
||||
|
||||
export type CombineDerivedDelta<
|
||||
@ -687,7 +708,9 @@ function makeDerivedDataProviderInternal<
|
||||
? combineData(
|
||||
parts.map((part) => part.data),
|
||||
variables,
|
||||
data
|
||||
data,
|
||||
parts,
|
||||
subscriptions
|
||||
)
|
||||
: data;
|
||||
if (
|
||||
|
Loading…
Reference in New Issue
Block a user