feat(#2565): orderbook populate limit (#2690)

Co-authored-by: asiaznik <artur@vegaprotocol.io>
This commit is contained in:
m.ray 2023-01-25 14:38:26 -05:00 committed by GitHub
parent dad953b45b
commit 7ea7edc1e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 207 additions and 87 deletions

View File

@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js';
import create from 'zustand';
import { create } from 'zustand';
import type { UserTrancheBalance } from '../../contexts/app-state/app-state-context';
export interface AssociationBreakdown {

View File

@ -1,6 +1,6 @@
import type ethers from 'ethers';
import type { GetState, SetState } from 'zustand';
import create from 'zustand';
import { create } from 'zustand';
export interface TxData {
tx: ethers.ContractTransaction;

View File

@ -43,8 +43,8 @@ export const HeaderStat = ({
testId?: string;
}) => {
const itemClass =
'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-default';
const itemHeading = 'text-neutral-500 dark:text-neutral-400';
'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-default text-neutral-500 dark:text-neutral-400';
const itemHeading = 'text-black dark:text-white';
return (
<div data-testid={testId} className={itemClass}>

View File

@ -95,7 +95,7 @@ const Details = ({
title?: string;
}) => (
<div className="pt-[5px]" data-testid="vega-tx-details" title={title}>
<div className="font-mono text-xs p-2 bg-neutral-100 rounded">
<div className="font-mono text-xs p-2 bg-neutral-100 rounded dark:bg-neutral-700 dark:text-white">
{children}
</div>
</div>

View File

@ -1,5 +1,5 @@
import { LocalStorage } from '@vegaprotocol/react-helpers';
import create from 'zustand';
import { create } from 'zustand';
import produce from 'immer';
interface GlobalStore {

View File

@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/react-helpers';
import { useAssetsDataProvider } from './assets-data-provider';
import { Button, Dialog, Icon, Splash } from '@vegaprotocol/ui-toolkit';
import create from 'zustand';
import { create } from 'zustand';
import { AssetDetailsTable } from './asset-details-table';
import { AssetProposalNotification } from '@vegaprotocol/governance';

View File

@ -15,7 +15,6 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import { usePersistedOrderStore } from '../../hooks/use-persisted-order';
import {
getDefaultOrder,
validateMarketState,
@ -27,6 +26,10 @@ import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
import { SummaryValidationType } from '../../constants';
import { useHasNoBalance } from '../../hooks/use-has-no-balance';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
import {
usePersistedOrderStore,
usePersistedOrderStoreSubscription,
} from '@vegaprotocol/orders';
export type TransactionStatus = 'default' | 'pending';
@ -43,13 +46,13 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
export const DealTicket = ({ market, submit }: DealTicketProps) => {
const { pubKey } = useVegaWallet();
// const [persistedOrder, setPersistedOrder] = usePersistedOrder(market);
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
(store) => ({
getPersistedOrder: store.getOrder,
setPersistedOrder: store.setOrder,
})
);
const {
register,
control,
@ -58,11 +61,23 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
setError,
clearErrors,
formState: { errors },
setValue,
} = useForm<DealTicketFormFields>({
defaultValues: getPersistedOrder(market.id) || getDefaultOrder(market),
});
const order = watch();
watch((orderData) => {
setPersistedOrder(orderData as DealTicketFormFields);
});
usePersistedOrderStoreSubscription(market.id, (storedOrder) => {
if (order.price !== storedOrder.price) {
setValue('price', storedOrder.price);
}
});
const marketStateError = validateMarketState(market.data.marketState);
const hasNoBalance = useHasNoBalance(
market.tradableInstrument.instrument.product.settlementAsset.id
@ -90,9 +105,6 @@ export const DealTicket = ({ market, submit }: DealTicketProps) => {
errors.summary?.type,
]);
// When order state changes persist it in local storage
useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]);
const onSubmit = useCallback(
(order: OrderSubmissionBody['orderSubmission']) => {
if (!pubKey) {

View File

@ -1,43 +0,0 @@
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import produce from 'immer';
import create from 'zustand';
import { persist } from 'zustand/middleware';
type OrderData = OrderSubmissionBody['orderSubmission'] | null;
type PersistedOrderStore = {
orders: OrderData[];
getOrder: (marketId: string) => OrderData | undefined;
setOrder: (order: OrderData) => void;
clear: () => void;
};
export const usePersistedOrderStore = create(
persist<PersistedOrderStore>(
(set, get) => ({
orders: [],
getOrder: (marketId) => {
const persisted = get().orders.find((o) => o?.marketId === marketId);
return persisted;
},
setOrder: (order) => {
set(
produce((store: PersistedOrderStore) => {
const persisted = store.orders.find(
(o) => o?.marketId === order?.marketId
);
if (persisted) {
Object.assign(persisted, order);
} else {
store.orders.push(order);
}
})
);
},
clear: () => set({ orders: [] }),
}),
{
name: 'VEGA_DEAL_TICKET_ORDER_STORE',
}
)
);

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import { t } from '@vegaprotocol/react-helpers';
import type { Intent } from '@vegaprotocol/ui-toolkit';
import { Dialog } from '@vegaprotocol/ui-toolkit';

View File

@ -1,7 +1,7 @@
import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Orderbook } from './orderbook';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { addDecimal, useDataProvider } from '@vegaprotocol/react-helpers';
import { marketDepthProvider } from './market-depth-provider';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
@ -16,6 +16,7 @@ import {
mapMarketData,
} from './orderbook-data';
import type { OrderbookData } from './orderbook-data';
import { usePersistedOrderStore } from '@vegaprotocol/orders';
interface OrderbookManagerProps {
marketId: string;
@ -122,7 +123,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
marketDataRef.current = marketData;
useEffect(() => {
const throttleRunnner = updateOrderbookData.current;
const throttleRunner = updateOrderbookData.current;
if (!marketDataRef.current) {
return;
}
@ -139,7 +140,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
setOrderbookData(dataRef.current);
return () => {
throttleRunnner.cancel();
throttleRunner.cancel();
};
}, [data, marketData, resolution]);
@ -148,6 +149,8 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
flush();
}, [resolution, flush]);
const updatePrice = usePersistedOrderStore((store) => store.updatePrice);
return (
<AsyncRenderer
loading={loading || marketDataLoading || marketLoading}
@ -160,6 +163,12 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
positionDecimalPlaces={market?.positionDecimalPlaces ?? 0}
resolution={resolution}
onResolutionChange={(resolution: number) => setResolution(resolution)}
onClick={(price?: string | number) => {
if (price) {
const priceValue = addDecimal(price, market?.decimalPlaces ?? 0);
updatePrice(marketId, priceValue);
}
}}
/>
</AsyncRenderer>
);

View File

@ -21,6 +21,7 @@ interface OrderbookRowProps {
price: string;
relativeAsk?: number;
relativeBid?: number;
onClick?: (price?: string | number) => void;
}
export const OrderbookRow = React.memo(
@ -37,6 +38,7 @@ export const OrderbookRow = React.memo(
price,
relativeAsk,
relativeBid,
onClick,
}: OrderbookRowProps) => {
return (
<>
@ -57,6 +59,7 @@ export const OrderbookRow = React.memo(
<PriceCell
testId={`price-${price}`}
value={BigInt(price)}
onClick={() => onClick && onClick(price)}
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
/>
<CumulativeVol

View File

@ -30,6 +30,7 @@ interface OrderbookProps extends OrderbookData {
positionDecimalPlaces: number;
resolution: number;
onResolutionChange: (resolution: number) => void;
onClick?: (price?: string | number) => void;
fillGaps?: boolean;
}
@ -279,6 +280,7 @@ export const Orderbook = ({
resolution,
fillGaps: initialFillGaps,
onResolutionChange,
onClick,
}: OrderbookProps) => {
const { theme } = useThemeSwitcher();
const scrollElement = useRef<HTMLDivElement>(null);
@ -533,6 +535,7 @@ export const Orderbook = ({
<OrderbookRow
key={data.price}
price={(BigInt(data.price) / BigInt(resolution)).toString()}
onClick={onClick}
decimalPlaces={decimalPlaces - Math.log10(resolution)}
positionDecimalPlaces={positionDecimalPlaces}
bid={data.bid}

View File

@ -245,7 +245,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
colId="amend"
headerName=""
field="status"
minWidth={150}
minWidth={100}
type="rightAligned"
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
return data && isOrderAmendable(data) ? (

View File

@ -4,3 +4,4 @@ export * from './use-order-cancel';
export * from './use-order-submit';
export * from './use-order-edit';
export * from './use-order-event';
export * from './use-persisted-order';

View File

@ -0,0 +1,78 @@
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import produce from 'immer';
import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware';
import isEqual from 'lodash/isEqual';
import { useEffect } from 'react';
type OrderData = OrderSubmissionBody['orderSubmission'] | null;
type PersistedOrderStore = {
orders: OrderData[];
getOrder: (marketId: string) => OrderData | undefined;
setOrder: (order: OrderData) => void;
clear: () => void;
updatePrice: (marketId: string, price: string) => void;
};
export const usePersistedOrderStore = create<PersistedOrderStore>()(
persist(
subscribeWithSelector((set, get) => ({
orders: [],
getOrder: (marketId: string) => {
const current = get() as PersistedOrderStore;
const persisted = current.orders.find((o) => o?.marketId === marketId);
return persisted;
},
setOrder: (order: OrderData) => {
set(
produce((store: PersistedOrderStore) => {
const persisted = store.orders.find(
(o) => o?.marketId === order?.marketId
);
if (persisted) {
if (!isEqual(persisted, order)) {
Object.assign(persisted, order);
} else {
// NOOP
}
} else {
store.orders.push(order);
}
})
);
},
clear: () => set({ orders: [] }),
updatePrice: (marketId: string, price: string) =>
set(
produce((store: PersistedOrderStore) => {
const persisted = store.orders.find(
(o) => o?.marketId === marketId
);
if (persisted) {
persisted.price = price;
}
})
),
})),
{
name: 'VEGA_DEAL_TICKET_ORDER_STORE',
}
)
);
export const usePersistedOrderStoreSubscription = (
marketId: string,
onOrderChange: (order: NonNullable<OrderData>) => void
) => {
const selector = (state: PersistedOrderStore) =>
state.orders.find((o) => o?.marketId === marketId);
const action = (storedOrder: OrderData | undefined) => {
if (storedOrder) {
onOrderChange(storedOrder);
}
};
const unsubscribe = usePersistedOrderStore.subscribe(selector, action);
useEffect(() => () => unsubscribe(), [unsubscribe]);
};

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import { LocalStorage } from '../lib/storage';
const THEME_STORAGE_KEY = 'theme';

View File

@ -4,11 +4,12 @@ export interface IPriceCellProps {
value: number | bigint | null | undefined;
valueFormatted: string;
testId?: string;
onClick?: (price?: string | number) => void;
}
export const PriceCell = memo(
forwardRef<HTMLSpanElement, IPriceCellProps>(
({ value, valueFormatted, testId }: IPriceCellProps, ref) => {
({ value, valueFormatted, testId, onClick }: IPriceCellProps, ref) => {
if (!isNumeric(value)) {
return (
<span data-testid="price" ref={ref}>
@ -20,7 +21,25 @@ export const PriceCell = memo(
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
return (
return onClick ? (
<button
onClick={() => onClick(value)}
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
>
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId || 'price'}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
</button>
) : (
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"

View File

@ -10,6 +10,7 @@ import { MAX_TRADES, tradesWithMarketProvider } from './trades-data-provider';
import { TradesTable } from './trades-table';
import type { Trade, TradeEdge } from './trades-data-provider';
import type { TradesQueryVariables } from './__generated__/Trades';
import { usePersistedOrderStore } from '@vegaprotocol/orders';
interface TradesContainerProps {
marketId: string;
@ -21,6 +22,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0);
const scrolledToTop = useRef(true);
const updatePrice = usePersistedOrderStore((store) => store.updatePrice);
const variables = useMemo<TradesQueryVariables>(
() => ({ marketId, maxTrades: MAX_TRADES }),
@ -113,6 +115,11 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
onClick={(price?: string) => {
if (price) {
updatePrice(marketId, price);
}
}}
/>
</AsyncRenderer>
);

View File

@ -1,6 +1,7 @@
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { forwardRef } from 'react';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import {
addDecimal,
@ -49,6 +50,7 @@ export interface Datasource extends IDatasource {
interface Props extends AgGridReactProps {
rowData?: Trade[] | null;
datasource?: Datasource;
onClick?: (price?: string) => void;
}
type TradesTableValueFormatterParams = Omit<
@ -87,6 +89,27 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
}
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
}}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<Trade, 'price'>) => {
if (!data?.market || !value) {
return null;
}
return (
<button
onClick={() =>
props.onClick &&
props.onClick(
addDecimal(value, data.market?.decimalPlaces || 0)
)
}
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
>
{addDecimalsFormatNumber(value, data.market.decimalPlaces)}
</button>
);
}}
/>
<AgGridColumn
headerName={t('Size')}

View File

@ -133,7 +133,7 @@ export const Toast = ({
)}
</div>
<div
className="flex-1 p-2 pr-6 text-sm overflow-auto"
className="flex-1 p-2 pr-6 text-sm overflow-auto dark:bg-black dark:text-white"
data-testid="toast-content"
>
{content}

View File

@ -7,7 +7,7 @@ import random from 'lodash/random';
import sample from 'lodash/sample';
import uniqueId from 'lodash/uniqueId';
import { useToasts } from './use-toasts';
import create from 'zustand';
import { create } from 'zustand';
import { useEffect } from '@storybook/addons';
import { formatNumber } from '@vegaprotocol/react-helpers';

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import type { Toast } from './toast';
type ToastsStore = {

View File

@ -1,12 +1,12 @@
import { act } from 'react-dom/test-utils';
const actualCreate = jest.requireActual('zustand').default; // if using jest
const zu = jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set();
// when creating a store, we get its initial state, create a reset function and add it in the set
const create = (createState) => {
const store = actualCreate(createState);
export const create = (createState) => {
const store = zu.create(createState);
const initialState = store.getState();
storeResetFns.add(() => store.setState(initialState, true));
return store;
@ -16,5 +16,3 @@ const create = (createState) => {
beforeEach(() => {
act(() => storeResetFns.forEach((resetFn) => resetFn()));
});
export default create;

View File

@ -23,10 +23,13 @@ import { ChainIdDocument } from '@vegaprotocol/react-helpers';
const mockUpdateDialogOpen = jest.fn();
const mockCloseVegaDialog = jest.fn();
jest.mock('zustand', () => () => () => ({
updateVegaWalletDialog: mockUpdateDialogOpen,
closeVegaWalletDialog: mockCloseVegaDialog,
vegaWalletDialogOpen: true,
jest.mock('zustand', () => ({
create: () => () => ({
updateVegaWalletDialog: mockUpdateDialogOpen,
closeVegaWalletDialog: mockCloseVegaDialog,
vegaWalletDialogOpen: true,
}),
}));
let defaultProps: VegaConnectDialogProps;

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import {
Button,
Dialog,

View File

@ -9,7 +9,7 @@ import {
} from './connectors';
import { determineId } from './utils';
import create from 'zustand';
import { create } from 'zustand';
import type { VegaTxState } from './use-vega-transaction';
import { VegaTxStatus } from './use-vega-transaction';
import type {

View File

@ -1,12 +1,12 @@
import { act } from 'react-dom/test-utils';
const actualCreate = jest.requireActual('zustand').default; // if using jest
const zu = jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set();
// when creating a store, we get its initial state, create a reset function and add it in the set
const create = (createState) => {
const store = actualCreate(createState);
export const create = (createState) => {
const store = zu.create(createState);
const initialState = store.getState();
storeResetFns.add(() => store.setState(initialState, true));
return store;

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import produce from 'immer';
import type { MultisigControl } from '@vegaprotocol/smart-contracts';
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import produce from 'immer';
import type BigNumber from 'bignumber.js';
import type { WithdrawalBusEventFieldsFragment } from '@vegaprotocol/wallet';

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import type { Web3ReactHooks } from '@web3-react/core';
import type { Connector } from '@web3-react/types';

View File

@ -1,6 +1,6 @@
import type { Asset } from '@vegaprotocol/assets';
import BigNumber from 'bignumber.js';
import create from 'zustand';
import { create } from 'zustand';
export interface WithdrawStore {
asset: Asset | undefined;

View File

@ -1,4 +1,4 @@
import create from 'zustand';
import { create } from 'zustand';
import { t } from '@vegaprotocol/react-helpers';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';

View File

@ -85,7 +85,7 @@
"uuid": "^8.3.2",
"web-vitals": "^2.1.4",
"zod": "^3.17.3",
"zustand": "^4.0.0-rc.1"
"zustand": "^4.3.2"
},
"devDependencies": {
"@apollo/react-testing": "^4.0.0",

View File

@ -23160,13 +23160,20 @@ zod@^3.17.3:
resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.1.tgz#112f074a97b50bfc4772d4ad1576814bd8ac4473"
integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==
zustand@^4.0.0-beta.2, zustand@^4.0.0-rc.1:
zustand@^4.0.0-beta.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.2.tgz#4912b24741662d8a84ed1cb52198471cb369c4b6"
integrity sha512-gcRaKchcxFPbImrBb/BKgujOhHhik9YhVpIeP87ETT7uokEe2Szu7KkuZ9ghjtD+/KKkcrRNktR2AiLXPIbKIQ==
dependencies:
use-sync-external-store "1.2.0"
zustand@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39"
integrity sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==
dependencies:
use-sync-external-store "1.2.0"
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"