feat: [console-lite] - improve useOrderBook hook (#1208)
* feat: [console-lite] - improve useOrderBook hook * feat: [console-lite] - improve useOrderBook hook - add unit test * feat: [console-lite] - improve useOrderBook hook - add unit test * feat: [console-lite] - improve useOrderBook hook - reduce mock size * feat: [console-lite] - improve useOrderBook hook - small clean up * feat: [console-lite] - improve useOrderBook hook - small clean up Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
parent
72ce253102
commit
02897f1ee1
@ -14,8 +14,7 @@ const useCalculateSlippage = ({ marketId, order }: Props) => {
|
||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||
const { data } = useOrderBookData({
|
||||
variables,
|
||||
resolution: 1,
|
||||
throttleMilliseconds: 10000,
|
||||
throttleMilliseconds: 5000,
|
||||
});
|
||||
const volPriceArr =
|
||||
data?.depth[order.side === Side.SIDE_BUY ? 'sell' : 'buy'] || [];
|
||||
|
132
libs/market-depth/src/lib/use-orderbook-data.spec.tsx
Normal file
132
libs/market-depth/src/lib/use-orderbook-data.spec.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type {
|
||||
MarketDepth_market,
|
||||
MarketDepth_market_data,
|
||||
} from './__generated__';
|
||||
import { useOrderBookData } from './use-orderbook-data';
|
||||
|
||||
const mockData: MarketDepth_market = {
|
||||
__typename: 'Market',
|
||||
id: 'marketId',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 0,
|
||||
data: {
|
||||
__typename: 'MarketData',
|
||||
staticMidPrice: '7820',
|
||||
marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
indicativeVolume: '0',
|
||||
indicativePrice: '0',
|
||||
bestStaticBidPrice: '7820',
|
||||
bestStaticOfferPrice: '7821',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'marketId',
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: { __typename: 'Trade', price: '7846' },
|
||||
sell: [
|
||||
{
|
||||
__typename: 'PriceLevel',
|
||||
price: '7861',
|
||||
volume: '25631',
|
||||
numberOfOrders: '4',
|
||||
},
|
||||
],
|
||||
buy: [
|
||||
{
|
||||
__typename: 'PriceLevel',
|
||||
price: '7820',
|
||||
volume: '28',
|
||||
numberOfOrders: '1',
|
||||
},
|
||||
],
|
||||
sequenceNumber: '1661857812317962664',
|
||||
},
|
||||
};
|
||||
|
||||
let updateMock: ({ data }: { data: MarketDepth_market }) => boolean;
|
||||
|
||||
const mockUseDataProvider = ({ update }: { update: () => boolean }) => {
|
||||
updateMock = update;
|
||||
return { data: mockData, loading: false, error: false };
|
||||
};
|
||||
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useDataProvider: jest.fn((args) => mockUseDataProvider(args)),
|
||||
}));
|
||||
|
||||
const modMock = (staticMidPrice: string): MarketDepth_market => {
|
||||
return {
|
||||
...mockData,
|
||||
data: {
|
||||
...mockData.data,
|
||||
staticMidPrice,
|
||||
} as MarketDepth_market_data,
|
||||
};
|
||||
};
|
||||
|
||||
describe('useOrderBookData hook', () => {
|
||||
it('should return proper data', () => {
|
||||
const { result } = renderHook(
|
||||
() => useOrderBookData({ variables: { marketId: 'marketId' } }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current.data).toEqual(mockData);
|
||||
});
|
||||
|
||||
it('should update data object', () => {
|
||||
const { result } = renderHook(
|
||||
() => useOrderBookData({ variables: { marketId: 'marketId' } }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('7820');
|
||||
|
||||
const updateMockData = modMock('1111');
|
||||
|
||||
act(() => {
|
||||
updateMock({ data: updateMockData });
|
||||
});
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('1111');
|
||||
});
|
||||
|
||||
it('throttling should delay update', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useOrderBookData({
|
||||
variables: { marketId: 'marketId' },
|
||||
throttleMilliseconds: 500,
|
||||
}),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('7820');
|
||||
|
||||
const updateMockData = modMock('2222');
|
||||
const updateMockData2 = modMock('3333');
|
||||
|
||||
await act(async () => {
|
||||
updateMock({ data: updateMockData });
|
||||
updateMock({ data: updateMockData2 });
|
||||
});
|
||||
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('2222');
|
||||
await new Promise((res) => {
|
||||
setTimeout(res, 400);
|
||||
});
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('2222');
|
||||
await new Promise((res) => {
|
||||
setTimeout(res, 200);
|
||||
});
|
||||
expect(result.current.data?.data?.staticMidPrice).toEqual('3333');
|
||||
});
|
||||
});
|
@ -1,84 +1,44 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
compactRows,
|
||||
updateCompactedRows,
|
||||
mapMarketData,
|
||||
} from './orderbook-data';
|
||||
import dataProvider from './market-depth-data-provider';
|
||||
import type { OrderbookData } from './orderbook-data';
|
||||
import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription';
|
||||
import type { MarketDepth_market } from './__generated__';
|
||||
|
||||
interface Props {
|
||||
variables: { marketId: string };
|
||||
resolution: number;
|
||||
throttleMilliseconds?: number;
|
||||
}
|
||||
|
||||
export const useOrderBookData = ({
|
||||
variables,
|
||||
resolution,
|
||||
throttleMilliseconds = 1000,
|
||||
}: Props) => {
|
||||
const [orderbookData, setOrderbookData] = useState<OrderbookData>({
|
||||
rows: null,
|
||||
});
|
||||
const resolutionRef = useRef(resolution);
|
||||
const dataRef = useRef<OrderbookData>({ rows: null });
|
||||
const deltaRef = useRef<MarketDepthSubscription_marketDepthUpdate>();
|
||||
const [orderbookData, setOrderbookData] = useState<MarketDepth_market | null>(
|
||||
null
|
||||
);
|
||||
const dataRef = useRef<MarketDepth_market | null>(null);
|
||||
const updateOrderbookData = useRef(
|
||||
throttle(() => {
|
||||
if (!deltaRef.current) {
|
||||
if (!dataRef.current) {
|
||||
return;
|
||||
}
|
||||
dataRef.current = {
|
||||
...deltaRef.current.market.data,
|
||||
...mapMarketData(deltaRef.current.market.data, resolutionRef.current),
|
||||
rows: updateCompactedRows(
|
||||
dataRef.current.rows ?? [],
|
||||
deltaRef.current.sell,
|
||||
deltaRef.current.buy,
|
||||
resolutionRef.current
|
||||
),
|
||||
};
|
||||
deltaRef.current = undefined;
|
||||
setOrderbookData(dataRef.current);
|
||||
}, throttleMilliseconds)
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => {
|
||||
if (!dataRef.current.rows) {
|
||||
({ data }: { data: MarketDepth_market | null }) => {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
if (deltaRef.current) {
|
||||
deltaRef.current.market = delta.market;
|
||||
if (delta.sell) {
|
||||
if (deltaRef.current.sell) {
|
||||
deltaRef.current.sell.push(...delta.sell);
|
||||
} else {
|
||||
deltaRef.current.sell = delta.sell;
|
||||
}
|
||||
}
|
||||
if (delta.buy) {
|
||||
if (deltaRef.current.buy) {
|
||||
deltaRef.current.buy.push(...delta.buy);
|
||||
} else {
|
||||
deltaRef.current.buy = delta.buy;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deltaRef.current = delta;
|
||||
}
|
||||
dataRef.current = data;
|
||||
updateOrderbookData.current();
|
||||
return true;
|
||||
},
|
||||
// using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change
|
||||
[]
|
||||
);
|
||||
|
||||
const { data, error, loading, flush } = useDataProvider({
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider,
|
||||
update,
|
||||
variables,
|
||||
@ -87,38 +47,22 @@ export const useOrderBookData = ({
|
||||
useEffect(() => {
|
||||
const throttleRunnner = updateOrderbookData.current;
|
||||
if (!data) {
|
||||
dataRef.current = { rows: null };
|
||||
dataRef.current = null;
|
||||
setOrderbookData(dataRef.current);
|
||||
return;
|
||||
}
|
||||
dataRef.current = {
|
||||
...data.data,
|
||||
rows: compactRows(data.depth.sell, data.depth.buy, resolution),
|
||||
...mapMarketData(data.data, resolution),
|
||||
...data,
|
||||
};
|
||||
setOrderbookData(dataRef.current);
|
||||
|
||||
return () => {
|
||||
throttleRunnner.cancel();
|
||||
};
|
||||
}, [data, resolution]);
|
||||
|
||||
useEffect(() => {
|
||||
resolutionRef.current = resolution;
|
||||
flush();
|
||||
}, [resolution, flush]);
|
||||
|
||||
const dataProps = useMemo(
|
||||
() => ({
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
}),
|
||||
[data, loading, error]
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
...dataProps,
|
||||
orderbookData,
|
||||
loading,
|
||||
error,
|
||||
data: orderbookData,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user