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:
macqbat 2022-09-02 11:29:52 +02:00 committed by GitHub
parent 72ce253102
commit 02897f1ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 150 additions and 75 deletions

View File

@ -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'] || [];

View 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');
});
});

View File

@ -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,
};
};