fix(trading): refactor market info accordion to avoid remount on candle reload (#3447)

This commit is contained in:
Bartłomiej Głownia 2023-04-20 11:32:09 +02:00 committed by GitHub
parent 460ccdb3a2
commit c15051d457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 352 additions and 224 deletions

View File

@ -50,7 +50,7 @@ jobs:
secrets: inherit secrets: inherit
lint-test-build: lint-test-build:
timeout-minutes: 20 timeout-minutes: 60
needs: node-modules needs: node-modules
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
name: '(CI) lint + unit test + build' name: '(CI) lint + unit test + build'

View File

@ -60,9 +60,11 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<> <>
<MarketInfoTable <MarketInfoTable
noBorder={false} noBorder={false}
data={trigger} data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces} decimalPlaces={market.decimalPlaces}
omits={['referencePrice', '__typename']}
/> />
<MarketInfoTable <MarketInfoTable
noBorder={false} noBorder={false}

View File

@ -108,18 +108,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('risk model displayed', () => { it('risk model displayed', () => {
cy.getByTestId(marketTitle).contains('Risk model').click(); cy.getByTestId(marketTitle).contains('Risk model').click();
validateMarketDataRow(0, 'Tau', '0.0001140771161');
validateMarketDataRow(0, 'Typename', 'LogNormalRiskModel'); validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
validateMarketDataRow(1, 'Tau', '0.0001140771161');
validateMarketDataRow(2, 'Risk Aversion Parameter', '0.01');
}); });
it('risk parameters displayed', () => { it('risk parameters displayed', () => {
cy.getByTestId(marketTitle).contains('Risk parameters').click(); cy.getByTestId(marketTitle).contains('Risk parameters').click();
validateMarketDataRow(0, 'R', '0.016');
validateMarketDataRow(0, 'Typename', 'LogNormalModelParams'); validateMarketDataRow(1, 'Sigma', '0.3');
validateMarketDataRow(1, 'R', '0.016');
validateMarketDataRow(2, 'Sigma', '0.3');
}); });
it('risk factors displayed', () => { it('risk factors displayed', () => {

View File

@ -1,5 +1,5 @@
import { DealTicketContainer } from '@vegaprotocol/deal-ticket'; import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import { MarketInfoContainer } from '@vegaprotocol/market-info'; import { MarketInfoAccordionContainer } from '@vegaprotocol/market-info';
import { OrderbookContainer } from '@vegaprotocol/market-depth'; import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { OrderListContainer } from '@vegaprotocol/orders'; import { OrderListContainer } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills'; import { FillsContainer } from '@vegaprotocol/fills';
@ -38,7 +38,7 @@ type MarketDependantView =
| typeof CandlesChartContainer | typeof CandlesChartContainer
| typeof DepthChartContainer | typeof DepthChartContainer
| typeof DealTicketContainer | typeof DealTicketContainer
| typeof MarketInfoContainer | typeof MarketInfoAccordionContainer
| typeof OrderbookContainer | typeof OrderbookContainer
| typeof TradesContainer; | typeof TradesContainer;
@ -56,7 +56,7 @@ const TradingViews = {
Depth: requiresMarket(DepthChartContainer), Depth: requiresMarket(DepthChartContainer),
Liquidity: requiresMarket(LiquidityContainer), Liquidity: requiresMarket(LiquidityContainer),
Ticket: requiresMarket(DealTicketContainer), Ticket: requiresMarket(DealTicketContainer),
Info: requiresMarket(MarketInfoContainer), Info: requiresMarket(MarketInfoAccordionContainer),
Orderbook: requiresMarket(OrderbookContainer), Orderbook: requiresMarket(OrderbookContainer),
Trades: requiresMarket(TradesContainer), Trades: requiresMarket(TradesContainer),
Positions: PositionsContainer, Positions: PositionsContainer,

View File

@ -13,8 +13,7 @@ import type { OnCellClickHandler } from '../../components/select-market';
import { Header, HeaderStat } from '../../components/header'; import { Header, HeaderStat } from '../../components/header';
import { NO_MARKET } from './constants'; import { NO_MARKET } from './constants';
import { MarketMarkPrice } from '../../components/market-mark-price'; import { MarketMarkPrice } from '../../components/market-mark-price';
import { Last24hPriceChange } from '../../components/last-24h-price-change'; import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/market-info';
import { Last24hVolume } from '../../components/last-24h-volume';
import { MarketState } from '../../components/market-state'; import { MarketState } from '../../components/market-state';
import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode'; import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode';
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied'; import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';

View File

@ -14,9 +14,8 @@ import type { CandleClose } from '@vegaprotocol/types';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/market-list'; import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/market-list';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { MarketMarkPrice } from '../market-mark-price'; import { MarketMarkPrice } from '../market-mark-price';
import { Last24hPriceChange } from '../last-24h-price-change'; import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/market-info';
import { MarketTradingMode } from '../market-trading-mode'; import { MarketTradingMode } from '../market-trading-mode';
import { Last24hVolume } from '../last-24h-volume';
import { Links, Routes } from '../../pages/client-router'; import { Links, Routes } from '../../pages/client-router';
const ellipsisClasses = 'whitespace-nowrap overflow-hidden text-ellipsis'; const ellipsisClasses = 'whitespace-nowrap overflow-hidden text-ellipsis';

View File

@ -16,19 +16,18 @@ export const useInitialMargin = (
order?: OrderSubmissionBody['orderSubmission'] order?: OrderSubmissionBody['orderSubmission']
) => { ) => {
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const commonVariables = { marketId, partyId: pubKey || '' };
const { data: marketData } = useDataProvider({ const { data: marketData } = useDataProvider({
dataProvider: marketDataProvider, dataProvider: marketDataProvider,
variables: { marketId }, variables: { marketId },
}); });
const { data: activeVolumeAndMargin } = useDataProvider({ const { data: activeVolumeAndMargin } = useDataProvider({
dataProvider: volumeAndMarginProvider, dataProvider: volumeAndMarginProvider,
variables: commonVariables, variables: { marketId, partyId: pubKey || '' },
skip: !pubKey, skip: !pubKey,
}); });
const { data: marketInfo } = useDataProvider({ const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider, dataProvider: marketInfoProvider,
variables: commonVariables, variables: { marketId },
}); });
let totalMargin = '0'; let totalMargin = '0';
let margin = '0'; let margin = '0';

View File

@ -1,2 +1,4 @@
export * from './market-info'; export * from './market-info';
export * from './last-24h-price-change';
export * from './last-24h-volume';
export * from './fees-breakdown'; export * from './fees-breakdown';

View File

@ -9,7 +9,6 @@ import { PriceChangeCell } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types'; import type { CandleClose } from '@vegaprotocol/types';
import { marketCandlesProvider } from '@vegaprotocol/market-list'; import { marketCandlesProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props { interface Props {
marketId?: string; marketId?: string;
@ -28,18 +27,15 @@ export const Last24hPriceChange = ({
}: Props) => { }: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current }); const [ref, inView] = useInView({ root: inViewRoot?.current });
const yesterday = useYesterday(); const yesterday = useYesterday();
const { data, error } = useThrottledDataProvider( const { data, error } = useThrottledDataProvider({
{ dataProvider: marketCandlesProvider,
dataProvider: marketCandlesProvider, variables: {
variables: { marketId: marketId || '',
marketId: marketId || '', interval: Schema.Interval.INTERVAL_I1H,
interval: Schema.Interval.INTERVAL_I1H, since: new Date(yesterday).toISOString(),
since: new Date(yesterday).toISOString(),
},
skip: !marketId || !inView,
}, },
THROTTLE_UPDATE_TIME skip: !marketId || !inView,
); });
const candles = const candles =
data data

View File

@ -10,7 +10,6 @@ import {
useYesterday, useYesterday,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props { interface Props {
marketId?: string; marketId?: string;
@ -30,18 +29,15 @@ export const Last24hVolume = ({
const yesterday = useYesterday(); const yesterday = useYesterday();
const [ref, inView] = useInView({ root: inViewRoot?.current }); const [ref, inView] = useInView({ root: inViewRoot?.current });
const { data } = useThrottledDataProvider( const { data } = useThrottledDataProvider({
{ dataProvider: marketCandlesProvider,
dataProvider: marketCandlesProvider, variables: {
variables: { marketId: marketId || '',
marketId: marketId || '', interval: Schema.Interval.INTERVAL_I1H,
interval: Schema.Interval.INTERVAL_I1H, since: new Date(yesterday).toISOString(),
since: new Date(yesterday).toISOString(),
},
skip: !(inView && marketId),
}, },
THROTTLE_UPDATE_TIME skip: !(inView && marketId),
); });
const candleVolume = data ? calcCandleVolume(data) : initialValue; const candleVolume = data ? calcCandleVolume(data) : initialValue;
return ( return (
<span ref={ref}> <span ref={ref}>

View File

@ -1,5 +1,5 @@
export * from './info-key-value-table'; export * from './info-key-value-table';
export * from './info-market'; export * from './market-info-accordion';
export * from './tooltip-mapping'; export * from './tooltip-mapping';
export * from './__generated__/MarketInfo'; export * from './__generated__/MarketInfo';
export * from './market-info-data-provider'; export * from './market-info-data-provider';

View File

@ -17,7 +17,7 @@ import { tooltipMapping } from './tooltip-mapping';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
interface RowProps { interface RowProps {
field: string; field: string;
value: unknown; value: ReactNode;
decimalPlaces?: number; decimalPlaces?: number;
asPercentage?: boolean; asPercentage?: boolean;
unformatted?: boolean; unformatted?: boolean;
@ -36,8 +36,8 @@ const Row = ({
}: RowProps) => { }: RowProps) => {
const className = 'text-black dark:text-white text-sm !px-0'; const className = 'text-black dark:text-white text-sm !px-0';
const getFormattedValue = (value: unknown) => { const getFormattedValue = (value: ReactNode) => {
if (typeof value !== 'string' && typeof value !== 'number') return null; if (typeof value !== 'string' && typeof value !== 'number') return value;
if (unformatted || isNaN(Number(value))) { if (unformatted || isNaN(Number(value))) {
return value; return value;
} }
@ -50,7 +50,7 @@ const Row = ({
return `${formatNumber(Number(value))} ${assetSymbol}`; return `${formatNumber(Number(value))} ${assetSymbol}`;
}; };
const formattedValue: string | number | null = getFormattedValue(value); const formattedValue = getFormattedValue(value);
if (!formattedValue) return null; if (!formattedValue) return null;
return ( return (
@ -70,11 +70,10 @@ const Row = ({
}; };
export interface MarketInfoTableProps { export interface MarketInfoTableProps {
data: unknown; data: Record<string, ReactNode> | null | undefined;
decimalPlaces?: number; decimalPlaces?: number;
asPercentage?: boolean; asPercentage?: boolean;
unformatted?: boolean; unformatted?: boolean;
omits?: string[];
children?: ReactNode; children?: ReactNode;
assetSymbol?: string; assetSymbol?: string;
noBorder?: boolean; noBorder?: boolean;
@ -85,7 +84,6 @@ export const MarketInfoTable = ({
decimalPlaces, decimalPlaces,
asPercentage, asPercentage,
unformatted, unformatted,
omits = ['__typename'],
children, children,
assetSymbol, assetSymbol,
noBorder, noBorder,
@ -96,20 +94,18 @@ export const MarketInfoTable = ({
return ( return (
<> <>
<KeyValueTable> <KeyValueTable>
{Object.entries(data) {Object.entries(data).map(([key, value]) => (
.filter(([key]) => !omits.includes(key)) <Row
.map(([key, value]) => ( key={key}
<Row field={key}
key={key} value={value}
field={key} decimalPlaces={decimalPlaces}
value={value} assetSymbol={assetSymbol}
decimalPlaces={decimalPlaces} asPercentage={asPercentage}
assetSymbol={assetSymbol} unformatted={unformatted}
asPercentage={asPercentage} noBorder={noBorder}
unformatted={unformatted} />
noBorder={noBorder} ))}
/>
))}
</KeyValueTable> </KeyValueTable>
<div className="flex flex-col gap-2">{children}</div> <div className="flex flex-col gap-2">{children}</div>
</> </>

View File

@ -1,7 +1,7 @@
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { removePaginationWrapper, TokenLinks } from '@vegaprotocol/utils'; import { removePaginationWrapper, TokenLinks } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {
Accordion, Accordion,
@ -11,12 +11,11 @@ import {
Splash, Splash,
TinyScroll, TinyScroll,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { generatePath, Link } from 'react-router-dom'; import { generatePath, Link } from 'react-router-dom';
import { marketInfoWithDataAndCandlesProvider } from './market-info-data-provider'; import { marketInfoProvider } from './market-info-data-provider';
import type { MarketInfoWithDataAndCandles } from './market-info-data-provider'; import type { MarketInfo } from './market-info-data-provider';
import { MarketProposalNotification } from '@vegaprotocol/proposals'; import { MarketProposalNotification } from '@vegaprotocol/proposals';
import { import {
CurrentFeesInfoPanel, CurrentFeesInfoPanel,
@ -37,8 +36,8 @@ import {
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from './market-info-panels'; } from './market-info-panels';
export interface InfoProps { export interface MarketInfoAccordionProps {
market: MarketInfoWithDataAndCandles; market: MarketInfo;
onSelect?: (id: string, metaKey?: boolean) => void; onSelect?: (id: string, metaKey?: boolean) => void;
} }
@ -46,34 +45,21 @@ export interface MarketInfoContainerProps {
marketId: string; marketId: string;
onSelect?: (id: string, metaKey?: boolean) => void; onSelect?: (id: string, metaKey?: boolean) => void;
} }
export const MarketInfoContainer = ({ export const MarketInfoAccordionContainer = ({
marketId, marketId,
onSelect, onSelect,
}: MarketInfoContainerProps) => { }: MarketInfoContainerProps) => {
const yesterday = useYesterday();
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const variables = useMemo(
() => ({
marketId,
since: yTimestamp,
interval: Schema.Interval.INTERVAL_I1H,
}),
[marketId, yTimestamp]
);
const { data, loading, error, reload } = useDataProvider({ const { data, loading, error, reload } = useDataProvider({
dataProvider: marketInfoWithDataAndCandlesProvider, dataProvider: marketInfoProvider,
skipUpdates: true, skipUpdates: true,
variables, variables: { marketId },
}); });
return ( return (
<AsyncRenderer data={data} loading={loading} error={error} reload={reload}> <AsyncRenderer data={data} loading={loading} error={error} reload={reload}>
{data ? ( {data ? (
<TinyScroll className="h-full overflow-auto"> <TinyScroll className="h-full overflow-auto">
<Info market={data} onSelect={onSelect} /> <MarketInfoAccordion market={data} onSelect={onSelect} />
</TinyScroll> </TinyScroll>
) : ( ) : (
<Splash> <Splash>
@ -84,7 +70,10 @@ export const MarketInfoContainer = ({
); );
}; };
export const Info = ({ market, onSelect }: InfoProps) => { const MarketInfoAccordion = ({
market,
onSelect,
}: MarketInfoAccordionProps) => {
const { VEGA_TOKEN_URL } = useEnvironment(); const { VEGA_TOKEN_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg'; const headerClassName = 'uppercase text-lg';

View File

@ -3,15 +3,8 @@ import type {
MarketInfoQuery, MarketInfoQuery,
MarketInfoQueryVariables, MarketInfoQueryVariables,
} from './__generated__/MarketInfo'; } from './__generated__/MarketInfo';
import { import { marketDataProvider } from '@vegaprotocol/market-list';
marketDataProvider, import type { MarketData, Candle } from '@vegaprotocol/market-list';
marketCandlesProvider,
} from '@vegaprotocol/market-list';
import type {
MarketData,
Candle,
MarketCandlesQueryVariables,
} from '@vegaprotocol/market-list';
import { MarketInfoDocument } from './__generated__/MarketInfo'; import { MarketInfoDocument } from './__generated__/MarketInfo';
export type MarketInfo = NonNullable<MarketInfoQuery['market']>; export type MarketInfo = NonNullable<MarketInfoQuery['market']>;
@ -49,20 +42,3 @@ export const marketInfoWithDataProvider = makeDerivedDataProvider<
} }
); );
}); });
export const marketInfoWithDataAndCandlesProvider = makeDerivedDataProvider<
MarketInfoWithDataAndCandles,
never,
MarketCandlesQueryVariables
>([marketInfoProvider, marketDataProvider, marketCandlesProvider], (parts) => {
const market: MarketInfo | null = parts[0];
const marketData: MarketData | null = parts[1];
const candles: Candle[] | null = parts[2];
return (
market && {
...market,
data: marketData || undefined,
candles: candles || undefined,
}
);
});

View File

@ -3,8 +3,8 @@ import { useMemo } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets'; import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
calcCandleVolume,
totalFeesPercentage, totalFeesPercentage,
marketDataProvider,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import { import {
@ -18,8 +18,8 @@ import { MarketInfoTable } from './info-key-value-table';
import type { import type {
MarketInfo, MarketInfo,
MarketInfoWithData, MarketInfoWithData,
MarketInfoWithDataAndCandles,
} from './market-info-data-provider'; } from './market-info-data-provider';
import { Last24hVolume } from '../last-24h-volume';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types'; import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
import { ConditionOperatorMapping } from '@vegaprotocol/types'; import { ConditionOperatorMapping } from '@vegaprotocol/types';
@ -27,6 +27,7 @@ import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import type { Provider } from '@vegaprotocol/oracles'; import type { Provider } from '@vegaprotocol/oracles';
import { useOracleProofs } from '@vegaprotocol/oracles'; import { useOracleProofs } from '@vegaprotocol/oracles';
import { useDataProvider } from '@vegaprotocol/react-helpers';
type PanelProps = Pick< type PanelProps = Pick<
ComponentProps<typeof MarketInfoTable>, ComponentProps<typeof MarketInfoTable>,
@ -37,14 +38,6 @@ type MarketInfoProps = {
market: MarketInfo; market: MarketInfo;
}; };
type MarketInfoWithDataProps = {
market: MarketInfoWithData;
};
type MarketInfoWithDataAndCandlesProps = {
market: MarketInfoWithDataAndCandles;
};
export const CurrentFeesInfoPanel = ({ export const CurrentFeesInfoPanel = ({
market, market,
...props ...props
@ -52,7 +45,9 @@ export const CurrentFeesInfoPanel = ({
<> <>
<MarketInfoTable <MarketInfoTable
data={{ data={{
...market.fees.factors, makerFee: market.fees.factors.makerFee,
infrastructureFee: market.fees.factors.infrastructureFee,
liquidityFee: market.fees.factors.liquidityFee,
totalFees: totalFeesPercentage(market.fees.factors), totalFees: totalFeesPercentage(market.fees.factors),
}} }}
asPercentage={true} asPercentage={true}
@ -69,18 +64,22 @@ export const CurrentFeesInfoPanel = ({
export const MarketPriceInfoPanel = ({ export const MarketPriceInfoPanel = ({
market, market,
...props ...props
}: MarketInfoWithDataProps & PanelProps) => { }: MarketInfoProps & PanelProps) => {
const assetSymbol = const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || ''; market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit = const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || ''; market?.tradableInstrument.instrument.product?.quoteName || '';
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return ( return (
<> <>
<MarketInfoTable <MarketInfoTable
data={{ data={{
markPrice: market.data?.markPrice, markPrice: data?.markPrice,
bestBidPrice: market.data?.bestBidPrice, bestBidPrice: data?.bestBidPrice,
bestOfferPrice: market.data?.bestOfferPrice, bestOfferPrice: data?.bestOfferPrice,
quoteUnit: market.tradableInstrument.instrument.product.quoteName, quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}} }}
decimalPlaces={market.decimalPlaces} decimalPlaces={market.decimalPlaces}
@ -99,8 +98,11 @@ export const MarketPriceInfoPanel = ({
export const MarketVolumeInfoPanel = ({ export const MarketVolumeInfoPanel = ({
market, market,
...props ...props
}: MarketInfoWithDataAndCandlesProps & PanelProps) => { }: MarketInfoProps & PanelProps) => {
const last24hourVolume = market.candles && calcCandleVolume(market.candles); const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const dash = (value: string | undefined) => const dash = (value: string | undefined) =>
value && value !== '0' ? value : '-'; value && value !== '0' ? value : '-';
@ -108,12 +110,17 @@ export const MarketVolumeInfoPanel = ({
return ( return (
<MarketInfoTable <MarketInfoTable
data={{ data={{
'24hourVolume': dash(last24hourVolume), '24hourVolume': (
openInterest: dash(market.data?.openInterest), <Last24hVolume
bestBidVolume: dash(market.data?.bestBidVolume), marketId={market.id}
bestOfferVolume: dash(market.data?.bestOfferVolume), positionDecimalPlaces={market.positionDecimalPlaces}
bestStaticBidVolume: dash(market.data?.bestStaticBidVolume), />
bestStaticOfferVolume: dash(market.data?.bestStaticOfferVolume), ),
openInterest: dash(data?.openInterest),
bestBidVolume: dash(data?.bestBidVolume),
bestOfferVolume: dash(data?.bestOfferVolume),
bestStaticBidVolume: dash(data?.bestStaticBidVolume),
bestStaticOfferVolume: dash(data?.bestStaticOfferVolume),
}} }}
decimalPlaces={market.positionDecimalPlaces} decimalPlaces={market.positionDecimalPlaces}
{...props} {...props}
@ -176,7 +183,7 @@ export const InstrumentInfoPanel = ({
marketName: market.tradableInstrument.instrument.name, marketName: market.tradableInstrument.instrument.name,
code: market.tradableInstrument.instrument.code, code: market.tradableInstrument.instrument.code,
productType: market.tradableInstrument.instrument.product.__typename, productType: market.tradableInstrument.instrument.product.__typename,
...market.tradableInstrument.instrument.product, quoteName: market.tradableInstrument.instrument.product.quoteName,
}} }}
{...props} {...props}
/> />
@ -239,38 +246,52 @@ export const MetadataInfoPanel = ({
export const RiskModelInfoPanel = ({ export const RiskModelInfoPanel = ({
market, market,
...props ...props
}: MarketInfoProps & PanelProps) => ( }: MarketInfoProps & PanelProps) => {
<MarketInfoTable if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') {
data={market.tradableInstrument.riskModel} return null;
unformatted={true} }
omits={[]} const { tau, riskAversionParameter } = market.tradableInstrument.riskModel;
{...props} return (
/> <MarketInfoTable
); data={{ tau, riskAversionParameter }}
unformatted
{...props}
/>
);
};
export const RiskParametersInfoPanel = ({ export const RiskParametersInfoPanel = ({
market, market,
...props ...props
}: MarketInfoProps & PanelProps) => ( }: MarketInfoProps & PanelProps) => {
<MarketInfoTable if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') {
data={market.tradableInstrument.riskModel.params} const { r, sigma, mu } = market.tradableInstrument.riskModel.params;
unformatted={true} return <MarketInfoTable data={{ r, sigma, mu }} unformatted {...props} />;
omits={[]} }
{...props} if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') {
/> const { factorLong, factorShort } =
); market.tradableInstrument.riskModel.params;
return (
<MarketInfoTable
data={{ factorLong, factorShort }}
unformatted
{...props}
/>
);
}
return null;
};
export const RiskFactorsInfoPanel = ({ export const RiskFactorsInfoPanel = ({
market, market,
...props ...props
}: MarketInfoProps & PanelProps) => ( }: MarketInfoProps & PanelProps) => {
<MarketInfoTable if (!market.riskFactors) {
data={market.riskFactors} return null;
unformatted={true} }
omits={['market', '__typename']} const { short, long } = market.riskFactors;
{...props} return <MarketInfoTable data={{ short, long }} unformatted {...props} />;
/> };
);
export const PriceMonitoringBoundsInfoPanel = ({ export const PriceMonitoringBoundsInfoPanel = ({
market, market,
@ -278,13 +299,17 @@ export const PriceMonitoringBoundsInfoPanel = ({
...props ...props
}: { }: {
triggerIndex: number; triggerIndex: number;
} & MarketInfoWithDataProps & } & MarketInfoProps &
PanelProps) => { PanelProps) => {
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const quoteUnit = const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || ''; market?.tradableInstrument.instrument.product?.quoteName || '';
const trigger = const trigger =
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex]; market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const bounds = market.data?.priceMonitoringBounds?.[triggerIndex]; const bounds = data?.priceMonitoringBounds?.[triggerIndex];
if (!trigger) { if (!trigger) {
console.error( console.error(
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})` `Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
@ -334,7 +359,11 @@ export const LiquidityMonitoringParametersInfoPanel = ({
<MarketInfoTable <MarketInfoTable
data={{ data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio, triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
...market.liquidityMonitoringParameters.targetStakeParameters, timeWindow:
market.liquidityMonitoringParameters.targetStakeParameters.timeWindow,
scalingFactor:
market.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}} }}
{...props} {...props}
/> />
@ -343,17 +372,21 @@ export const LiquidityMonitoringParametersInfoPanel = ({
export const LiquidityInfoPanel = ({ export const LiquidityInfoPanel = ({
market, market,
...props ...props
}: MarketInfoWithDataProps & PanelProps) => { }: MarketInfoProps & PanelProps) => {
const assetDecimals = const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals; market.tradableInstrument.instrument.product.settlementAsset.decimals;
const assetSymbol = const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || ''; market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return ( return (
<MarketInfoTable <MarketInfoTable
data={{ data={{
targetStake: market.data && market.data.targetStake, targetStake: data?.targetStake,
suppliedStake: market.data && market.data?.suppliedStake, suppliedStake: data?.suppliedStake,
marketValueProxy: market.data && market.data.marketValueProxy, marketValueProxy: data?.marketValueProxy,
}} }}
decimalPlaces={assetDecimals} decimalPlaces={assetDecimals}
assetSymbol={assetSymbol} assetSymbol={assetSymbol}
@ -365,12 +398,16 @@ export const LiquidityInfoPanel = ({
export const LiquidityPriceRangeInfoPanel = ({ export const LiquidityPriceRangeInfoPanel = ({
market, market,
...props ...props
}: MarketInfoWithDataProps & PanelProps) => { }: MarketInfoProps & PanelProps) => {
const quoteUnit = const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || ''; market?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage( const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100) new BigNumber(market.lpPriceRange).times(100)
); );
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
return ( return (
<> <>
<p className="text-xs mb-4"> <p className="text-xs mb-4">
@ -386,20 +423,20 @@ export const LiquidityPriceRangeInfoPanel = ({
data={{ data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`, liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice: lowestPrice:
market.data?.midPrice && data?.midPrice &&
`${addDecimalsFormatNumber( `${addDecimalsFormatNumber(
new BigNumber(1) new BigNumber(1)
.minus(market.lpPriceRange) .minus(market.lpPriceRange)
.times(market.data.midPrice) .times(data.midPrice)
.toString(), .toString(),
market.decimalPlaces market.decimalPlaces
)} ${quoteUnit}`, )} ${quoteUnit}`,
highestPrice: highestPrice:
market.data?.midPrice && data?.midPrice &&
`${addDecimalsFormatNumber( `${addDecimalsFormatNumber(
new BigNumber(1) new BigNumber(1)
.plus(market.lpPriceRange) .plus(market.lpPriceRange)
.times(market.data.midPrice) .times(data.midPrice)
.toString(), .toString(),
market.decimalPlaces market.decimalPlaces
)} ${quoteUnit}`, )} ${quoteUnit}`,
@ -418,7 +455,15 @@ export const OracleInfoPanel = ({
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment(); const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
const { data } = useOracleProofs(ORACLE_PROOFS_URL); const { data } = useOracleProofs(ORACLE_PROOFS_URL);
return ( return (
<MarketInfoTable data={product.dataSourceSpecBinding} {...props}> <MarketInfoTable
data={{
settlementDataProperty:
product.dataSourceSpecBinding.settlementDataProperty,
tradingTerminationProperty:
product.dataSourceSpecBinding.tradingTerminationProperty,
}}
{...props}
>
<div <div
className="flex flex-col gap-2 mt-4" className="flex flex-col gap-2 mt-4"
data-testid="oracle-proof-links" data-testid="oracle-proof-links"

View File

@ -354,10 +354,10 @@ export const volumeAndMarginProvider = makeDerivedDataProvider<
partyId, partyId,
marketId, marketId,
}), }),
(callback, client, variables) => (callback, client, { marketId }) =>
marketDataProvider(callback, client, { marketId: variables.marketId }), marketDataProvider(callback, client, { marketId }),
(callback, client, variables) => (callback, client, { marketId }) =>
marketInfoProvider(callback, client, { marketId: variables.marketId }), marketInfoProvider(callback, client, { marketId }),
openVolumeDataProvider, openVolumeDataProvider,
], ],
(data) => { (data) => {

View File

@ -6,7 +6,7 @@ import { MockedProvider } from '@apollo/client/testing';
type Data = number; type Data = number;
type Delta = number; type Delta = number;
type Variables = { partyId: string }; type Variables = { partyId: string; marketIds?: string[] };
const unsubscribe = jest.fn(); const unsubscribe = jest.fn();
const reload = jest.fn(); const reload = jest.fn();
@ -156,6 +156,121 @@ describe('useDataProvider hook', () => {
expect(insert.mock.calls[1][0].insertionData).toEqual(insertionData); expect(insert.mock.calls[1][0].insertionData).toEqual(insertionData);
}); });
it('calls dataProvider with updated variables if skip switched to true', async () => {
const { rerender } = render({
dataProvider,
variables: { partyId: '' },
skip: true,
});
expect(dataProvider).toBeCalledTimes(0);
rerender({
dataProvider,
variables,
});
expect(dataProvider).toBeCalledTimes(1);
expect(dataProvider.mock.calls[0][2]).toEqual(variables);
});
it('uses same data provider when rerendered with equal variables', async () => {
const { rerender } = render({
dataProvider,
variables: { ...variables, marketIds: ['a', 'b'] },
});
expect(dataProvider).toBeCalledTimes(1);
rerender({
dataProvider,
variables: { ...variables, marketIds: ['b', 'a'] },
});
expect(dataProvider).toBeCalledTimes(1);
rerender({
dataProvider,
variables: { ...variables },
});
expect(dataProvider).toBeCalledTimes(2);
});
it('calls new update and insert when replaced', async () => {
const { rerender } = render({
dataProvider,
update,
insert,
variables,
});
const data = 0;
const delta = 0;
const insertionData = 0;
const callback = dataProvider.mock.calls[0][0];
await act(async () => {
callback({ ...updateCallbackPayload, data });
});
const newUpdate = jest.fn();
const newInsert = jest.fn();
expect(update).toBeCalledTimes(2);
expect(insert).toBeCalledTimes(0);
rerender({ dataProvider, update: newUpdate, insert: newInsert, variables });
await act(async () => {
callback({
...updateCallbackPayload,
data: data,
delta,
isUpdate: true,
});
});
expect(newUpdate).toBeCalledTimes(1);
await act(async () => {
callback({
...updateCallbackPayload,
data,
insertionData,
isInsert: true,
});
});
expect(newUpdate).toBeCalledTimes(2);
expect(newInsert).toBeCalledTimes(1);
expect(update).toBeCalledTimes(2);
expect(insert).toBeCalledTimes(0);
});
it('skip updates if skipUpdates is true', async () => {
const { result, rerender } = render({
dataProvider,
update,
variables,
skipUpdates: true,
});
expect(update).toBeCalledTimes(1);
let data = 0;
const delta = 1;
const callback = dataProvider.mock.calls[0][0];
await act(async () => {
callback({ ...updateCallbackPayload, data });
});
expect(update).toBeCalledTimes(2);
expect(result.current.data).toEqual(data);
await act(async () => {
callback({
...updateCallbackPayload,
data: data + delta,
delta,
isUpdate: true,
});
});
expect(update).toBeCalledTimes(2);
expect(result.current.data).toEqual(data);
rerender({ dataProvider, variables, update });
expect(update).toBeCalledTimes(2);
await act(async () => {
callback({
...updateCallbackPayload,
data: (data = data + delta),
delta,
isUpdate: true,
});
});
expect(update).toBeCalledTimes(3);
expect(result.current.data).toEqual(data);
expect(dataProvider).toBeCalledTimes(1);
});
it('change data provider instance on variables change', async () => { it('change data provider instance on variables change', async () => {
const { result, rerender } = render({ dataProvider, update, variables }); const { result, rerender } = render({ dataProvider, update, variables });
const callback = dataProvider.mock.calls[0][0]; const callback = dataProvider.mock.calls[0][0];

View File

@ -63,6 +63,9 @@ export const useDataProvider = <
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined); const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
const loadRef = useRef<Load<Data> | undefined>(undefined); const loadRef = useRef<Load<Data> | undefined>(undefined);
const variablesRef = useRef<Variables>(props.variables); const variablesRef = useRef<Variables>(props.variables);
const updateRef = useRef(update);
const insertRef = useRef(insert);
const skipUpdatesRef = useRef(skipUpdates);
const variables = useMemo(() => { const variables = useMemo(() => {
if ( if (
!isEqualWith( !isEqualWith(
@ -91,54 +94,69 @@ export const useDataProvider = <
} }
return Promise.reject(); return Promise.reject();
}, []); }, []);
const callback = useCallback<UpdateCallback<Data, Delta>>( const callback = useCallback<UpdateCallback<Data, Delta>>((args) => {
(args) => { const {
const { data,
data, delta,
delta, error,
error, loading,
loading, insertionData,
insertionData, totalCount,
totalCount, isInsert,
isInsert, isUpdate,
isUpdate, } = args;
} = args; setError(error);
setError(error); setLoading(loading);
setLoading(loading); // if update or insert function returns true it means that component handles updates
// 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
// component can use flush() which will call callback without delta and cause data state update if (!loading) {
if (!loading) { if (
if ( isUpdate &&
isUpdate && (skipUpdatesRef.current ||
!skipUpdates && (!skipUpdatesRef.current &&
update && updateRef.current &&
update({ delta, data, totalCount }) updateRef.current({ delta, data, totalCount })))
) { ) {
return; return;
}
if (isInsert && insert && insert({ insertionData, data, totalCount })) {
return;
}
} }
setTotalCount(totalCount); if (
setData(data); isInsert &&
if (!loading && !isUpdate && update) { insertRef.current &&
update({ data }); insertRef.current({ insertionData, data, totalCount })
) {
return;
} }
}, }
[update, insert, skipUpdates] setTotalCount(totalCount);
); setData(data);
if (!loading && !isUpdate && updateRef.current) {
updateRef.current({ data });
}
}, []);
useEffect(() => {
updateRef.current = update;
}, [update]);
useEffect(() => {
insertRef.current = insert;
}, [insert]);
useEffect(() => {
skipUpdatesRef.current = skipUpdates;
}, [skipUpdates]);
useEffect(() => { useEffect(() => {
setData(null); setData(null);
setError(undefined); setError(undefined);
setTotalCount(undefined); setTotalCount(undefined);
if (update) { if (updateRef.current) {
update({ data: null }); updateRef.current({ data: null });
} }
if (skip) { if (skip) {
setLoading(false); setLoading(false);
if (update) { if (updateRef.current) {
update({ data: null }); updateRef.current({ data: null });
} }
return; return;
} }
@ -157,7 +175,7 @@ export const useDataProvider = <
loadRef.current = undefined; loadRef.current = undefined;
return unsubscribe(); return unsubscribe();
}; };
}, [client, dataProvider, callback, variables, skip, update]); }, [client, dataProvider, callback, variables, skip]);
return { return {
data, data,
loading, loading,
@ -175,7 +193,7 @@ export const useThrottledDataProvider = <
Variables extends OperationVariables = OperationVariables Variables extends OperationVariables = OperationVariables
>( >(
params: Omit<useDataProviderParams<Data, Delta, Variables>, 'update'>, params: Omit<useDataProviderParams<Data, Delta, Variables>, 'update'>,
wait?: number wait = 500
) => { ) => {
const [data, setData] = useState<Data | null>(null); const [data, setData] = useState<Data | null>(null);
const dataRef = useRef<Data | null>(null); const dataRef = useRef<Data | null>(null);