chore(markets): keep market list live updated (#3954)
This commit is contained in:
parent
0f2b86770a
commit
702ea0c6b4
@ -11,6 +11,8 @@ import type {
|
||||
UpdateCallback,
|
||||
Update,
|
||||
PageInfo,
|
||||
Reload,
|
||||
Load,
|
||||
} from './generic-data-provider';
|
||||
import type {
|
||||
ApolloClient,
|
||||
@ -600,6 +602,17 @@ describe('data provider', () => {
|
||||
});
|
||||
|
||||
describe('derived data provider', () => {
|
||||
let subscription: {
|
||||
unsubscribe: () => void;
|
||||
reload: Reload;
|
||||
flush: () => void;
|
||||
load?: Load<CombinedData>;
|
||||
};
|
||||
afterEach(() => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
it('memoize instance and unsubscribe if no subscribers', () => {
|
||||
clientSubscribeSubscribe.mockClear();
|
||||
clientSubscribeUnsubscribe.mockClear();
|
||||
@ -628,7 +641,7 @@ describe('derived data provider', () => {
|
||||
ReturnType<UpdateCallback<CombinedData, CombinedData>>,
|
||||
Parameters<UpdateCallback<CombinedData, CombinedData>>
|
||||
>();
|
||||
const subscription = derivedSubscribe(callback, client, variables);
|
||||
subscription = derivedSubscribe(callback, client, variables);
|
||||
const data = { totalCount: 0 };
|
||||
combineData.mockReturnValueOnce(data);
|
||||
expect(callback.mock.calls.length).toBe(0);
|
||||
@ -642,7 +655,6 @@ describe('derived data provider', () => {
|
||||
expect(callback.mock.calls.length).toBe(1);
|
||||
expect(callback.mock.calls[0][0].data).toBe(data);
|
||||
expect(callback.mock.calls[0][0].loading).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('callback with error if any dependency has error, reloads all dependencies on reload', async () => {
|
||||
@ -655,7 +667,7 @@ describe('derived data provider', () => {
|
||||
Parameters<UpdateCallback<CombinedData, CombinedData>>
|
||||
>();
|
||||
expect(callback.mock.calls.length).toBe(0);
|
||||
const subscription = derivedSubscribe(callback, client, variables);
|
||||
subscription = derivedSubscribe(callback, client, variables);
|
||||
const data = { totalCount: 0 };
|
||||
combineData.mockReturnValueOnce(data);
|
||||
expect(callback.mock.calls.length).toBe(0);
|
||||
@ -678,7 +690,6 @@ describe('derived data provider', () => {
|
||||
expect(callback.mock.calls[2][0].data).toStrictEqual(data);
|
||||
expect(callback.mock.calls[2][0].loading).toBe(false);
|
||||
expect(callback.mock.calls[2][0].error).toBeUndefined();
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('pass isUpdate on any dependency isUpdate, uses result of combineDelta as delta in next callback', async () => {
|
||||
@ -690,7 +701,7 @@ describe('derived data provider', () => {
|
||||
ReturnType<UpdateCallback<CombinedData, CombinedData>>,
|
||||
Parameters<UpdateCallback<CombinedData, CombinedData>>
|
||||
>();
|
||||
const subscription = derivedSubscribe(callback, client, variables);
|
||||
subscription = derivedSubscribe(callback, client, variables);
|
||||
const data = { totalCount: 0 };
|
||||
combineData.mockReturnValueOnce(data);
|
||||
await resolveQuery({ data: part1 });
|
||||
@ -711,7 +722,6 @@ describe('derived data provider', () => {
|
||||
expect(callback).toBeCalledTimes(2);
|
||||
expect(callback.mock.calls[1][0].isUpdate).toBe(true);
|
||||
expect(callback.mock.calls[1][0].delta).toBe(combinedDelta);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('pass isInsert on any dependency isInsert, uses result of combineInsertionData as insertionData in next callback', async () => {
|
||||
@ -722,7 +732,7 @@ describe('derived data provider', () => {
|
||||
ReturnType<UpdateCallback<CombinedData, CombinedData>>,
|
||||
Parameters<UpdateCallback<CombinedData, CombinedData>>
|
||||
>();
|
||||
const subscription = derivedSubscribe(callback, client, variables);
|
||||
subscription = derivedSubscribe(callback, client, variables);
|
||||
const data = { totalCount: 0 };
|
||||
combineData.mockReturnValueOnce(data);
|
||||
await resolveQuery({
|
||||
@ -758,6 +768,5 @@ describe('derived data provider', () => {
|
||||
expect(callback).toBeCalledTimes(2);
|
||||
expect(callback.mock.calls[1][0].isInsert).toBe(true);
|
||||
expect(callback.mock.calls[1][0].insertionData).toBe(combinedInsertionData);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
@ -443,8 +443,8 @@ function makeDataProviderInternal<
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
// hard reset on demand or when there is no apollo subscription yet
|
||||
if (forceReset || !subscription) {
|
||||
// hard reset on demand or when error occurs
|
||||
if (forceReset || error) {
|
||||
reset();
|
||||
initialize();
|
||||
} else {
|
||||
|
@ -104,9 +104,10 @@ export const useDataProvider = <
|
||||
totalCount,
|
||||
isInsert,
|
||||
isUpdate,
|
||||
loaded,
|
||||
} = args;
|
||||
setError(error);
|
||||
setLoading(loading);
|
||||
setLoading(!loaded && loading);
|
||||
// if update or insert function returns true it means that component handles updates
|
||||
// component can use flush() which will call callback without delta and cause data state update
|
||||
if (!loading) {
|
||||
|
@ -93,15 +93,14 @@ export const MarketListTable = forwardRef<
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return undefined;
|
||||
const { trigger } = data.data;
|
||||
const { tradingMode } = data;
|
||||
return tradingMode ===
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
return marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||
? `${Schema.MarketTradingModeMapping[tradingMode]}
|
||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||
: Schema.MarketTradingModeMapping[tradingMode];
|
||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
}}
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
@ -228,6 +227,7 @@ export const MarketListTable = forwardRef<
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="market-actions"
|
||||
field="id"
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({
|
||||
data,
|
||||
|
@ -1,37 +1,60 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
|
||||
const POLLING_TIME = 2000;
|
||||
interface MarketsContainerProps {
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [dataCount, setDataCount] = useState(0);
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider,
|
||||
skipUpdates: true,
|
||||
variables: undefined,
|
||||
});
|
||||
useEffect(() => {
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
}, [data]);
|
||||
const onFilterChanged = useCallback(() => {
|
||||
const dataRef = useRef<MarketMaybeWithData[] | null>(null);
|
||||
const [dataCount, setDataCount] = useState(1);
|
||||
const handleDataCount = useCallback(() => {
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
}, []);
|
||||
const update = useCallback(
|
||||
({ data }: { data: MarketMaybeWithData[] | null }) => {
|
||||
data && gridRef.current?.api?.setRowData(data);
|
||||
dataRef.current = data;
|
||||
handleDataCount();
|
||||
return true;
|
||||
},
|
||||
[handleDataCount]
|
||||
);
|
||||
|
||||
const { error, loading, reload } = useDataProvider({
|
||||
dataProvider,
|
||||
variables: undefined,
|
||||
update,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
const handleOnGridReady = useCallback(() => {
|
||||
dataRef?.current && update({ data: dataRef.current });
|
||||
handleDataCount();
|
||||
}, [handleDataCount, update]);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<MarketListTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
onCellClicked={(cellEvent: CellClickedEvent) => {
|
||||
@ -44,6 +67,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
||||
'id',
|
||||
'tradableInstrument.instrument.code',
|
||||
'tradableInstrument.instrument.product.settlementAsset',
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
].includes(colId)
|
||||
) {
|
||||
return;
|
||||
@ -55,13 +79,14 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
||||
);
|
||||
}}
|
||||
onMarketClick={onSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onFilterChanged={handleDataCount}
|
||||
onGridReady={handleOnGridReady}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
data={dataRef?.current || []}
|
||||
noDataMessage={t('No markets')}
|
||||
noDataCondition={() => !dataCount}
|
||||
reload={reload}
|
||||
|
@ -2,7 +2,7 @@ import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type { Market, Candle } from '../';
|
||||
import type { Market, Candle, MarketMaybeWithData } from '../';
|
||||
const { MarketState, MarketTradingMode } = Schema;
|
||||
|
||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
||||
@ -19,7 +19,7 @@ export const totalFeesPercentage = (fees: Market['fees']['factors']) => {
|
||||
return total ? formatNumberPercentage(total) : undefined;
|
||||
};
|
||||
|
||||
export const filterAndSortMarkets = (markets: Market[]) => {
|
||||
export const filterAndSortMarkets = (markets: MarketMaybeWithData[]) => {
|
||||
const tradingModesOrdering = [
|
||||
MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
@ -28,27 +28,30 @@ export const filterAndSortMarkets = (markets: Market[]) => {
|
||||
MarketTradingMode.TRADING_MODE_NO_TRADING,
|
||||
];
|
||||
const orderedMarkets = orderBy(
|
||||
markets?.filter(
|
||||
(m) =>
|
||||
m.state !== MarketState.STATE_REJECTED &&
|
||||
m.tradingMode !== MarketTradingMode.TRADING_MODE_NO_TRADING
|
||||
) || [],
|
||||
markets?.filter((m) => {
|
||||
const state = m.data?.marketState || m.state;
|
||||
const tradingMode = m.data?.marketTradingMode || m.tradingMode;
|
||||
return (
|
||||
state !== MarketState.STATE_REJECTED &&
|
||||
tradingMode !== MarketTradingMode.TRADING_MODE_NO_TRADING
|
||||
);
|
||||
}) || [],
|
||||
['marketTimestamps.open', 'id'],
|
||||
['asc', 'asc']
|
||||
);
|
||||
return orderedMarkets.sort(
|
||||
(a, b) =>
|
||||
tradingModesOrdering.indexOf(a.tradingMode) -
|
||||
tradingModesOrdering.indexOf(b.tradingMode)
|
||||
tradingModesOrdering.indexOf(a.data?.marketTradingMode || a.tradingMode) -
|
||||
tradingModesOrdering.indexOf(b.data?.marketTradingMode || b.tradingMode)
|
||||
);
|
||||
};
|
||||
|
||||
export const filterAndSortClosedMarkets = (markets: Market[]) => {
|
||||
export const filterAndSortClosedMarkets = (markets: MarketMaybeWithData[]) => {
|
||||
return markets.filter((m) => {
|
||||
return [
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
].includes(m.state);
|
||||
].includes(m.data?.marketState || m.state);
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user