fix(trading): market info refactor (#3985)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
m.ray 2023-06-06 05:00:29 +03:00 committed by GitHub
parent d6e2432955
commit 011e4e4cec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 381 additions and 521 deletions

View File

@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets'; import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { PriceMonitoringBoundsInfoPanel } from '@vegaprotocol/markets';
import { import {
LiquidityInfoPanel, LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel, LiquidityMonitoringParametersInfoPanel,
@ -39,133 +40,69 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return []; return [];
}; };
const oraclePanels = isEqual( const showTwoOracles = isEqual(
getSigners(settlementData), getSigners(settlementData),
getSigners(terminationData) getSigners(terminationData)
) );
? [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
]
: [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
];
const panels = [ const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
{
title: t('Key details'),
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel noBorder={false} market={market} />,
},
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable noBorder={false} data={trigger} />,
})
),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<>
<MarketInfoTable
noBorder={false}
data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
/>
<MarketInfoTable
noBorder={false}
data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
</>
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
<LiquidityMonitoringParametersInfoPanel
noBorder={false}
market={market}
/>
),
},
{
title: t('Liquidity'),
content: <LiquidityInfoPanel market={market} noBorder={false} />,
},
{
title: t('Liquidity price range'),
content: (
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
),
},
...oraclePanels,
];
return ( return (
<> <div>
{panels.map((p) => ( <h2 className={headerClassName}>{t('Key details')}</h2>
<div key={p.title} className="mb-3"> <KeyDetailsInfoPanel market={market} />
<h2 className="font-alpha calt text-xl">{p.title}</h2> <h2 className={headerClassName}>{t('Instrument')}</h2>
{p.content} <InstrumentInfoPanel market={market} />
</div> <h2 className={headerClassName}>{t('Settlement asset')}</h2>
<SettlementAssetInfoPanel market={market} />
<h2 className={headerClassName}>{t('Metadata')}</h2>
<MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
<RiskParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring bounds %s', [(i + 1).toString()])}
</h2>
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={i + 1}
/>
</>
))} ))}
</> {(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
{showTwoOracles ? (
<>
<h2 className={headerClassName}>{t('Settlement oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
<h2 className={headerClassName}>{t('Termination oracle')}</h2>
<OracleInfoPanel market={market} type="termination" />
</>
) : (
<>
<h2 className={headerClassName}>{t('Oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
</>
)}
</div>
); );
}; };

View File

@ -55,12 +55,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(3, 'Quote Unit', 'BTC'); validateMarketDataRow(3, 'Quote Unit', 'BTC');
}); });
// TODO: fix this test it('market volume displayed', () => {
// New volume check logic, added by https://github.com/vegaprotocol/frontend-monorepo/pull/3870 has caused the
// 24hr volume assertion to fail as it now reads 'Unknown'
it.skip('market volume displayed', () => {
cy.getByTestId(marketTitle).contains('Market volume').click(); cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(0, '24 Hour Volume', '1'); validateMarketDataRow(0, '24 Hour Volume', 'Unknown');
validateMarketDataRow(1, 'Open Interest', '-'); validateMarketDataRow(1, 'Open Interest', '-');
validateMarketDataRow(2, 'Best Bid Volume', '1'); validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3'); validateMarketDataRow(3, 'Best Offer Volume', '3');

View File

@ -34,7 +34,7 @@ const Row = ({
assetSymbol = '', assetSymbol = '',
noBorder = true, noBorder = true,
}: RowProps) => { }: RowProps) => {
const className = 'text-black dark:text-white text-sm !px-0'; const className = 'text-sm';
const getFormattedValue = (value: ReactNode) => { const getFormattedValue = (value: ReactNode) => {
if (typeof value !== 'string' && typeof value !== 'number') return value; if (typeof value !== 'string' && typeof value !== 'number') return value;

View File

@ -10,6 +10,7 @@ import {
Link as UILink, Link as UILink,
Splash, Splash,
TinyScroll, TinyScroll,
AccordionItem,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { generatePath, Link } from 'react-router-dom'; import { generatePath, Link } from 'react-router-dom';
@ -85,26 +86,6 @@ export const MarketInfoAccordion = ({
market.accountsConnection?.edges market.accountsConnection?.edges
); );
const marketDataPanels = [
{
title: t('Current fees'),
content: <CurrentFeesInfoPanel market={market} />,
},
{
title: t('Market price'),
content: <MarketPriceInfoPanel market={market} />,
},
{
title: t('Market volume'),
content: <MarketVolumeInfoPanel market={market} />,
},
...marketAccounts
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a) => ({
title: t(`Insurance pool`),
content: <InsurancePoolInfoPanel market={market} account={a} />,
})),
];
const settlementData = market.tradableInstrument.instrument.product const settlementData = market.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition; .dataSourceSpecForSettlementData.data as DataSourceDefinition;
const terminationData = market.tradableInstrument.instrument.product const terminationData = market.tradableInstrument.instrument.product
@ -123,165 +104,187 @@ export const MarketInfoAccordion = ({
} }
return []; return [];
}; };
const oraclePanels = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
]
: [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
];
const marketSpecPanels = [
{
title: t('Key details'),
content: <KeyDetailsInfoPanel market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel market={market} />,
},
...oraclePanels,
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(_, triggerIndex) => ({
title: t(`Price monitoring bounds ${triggerIndex + 1}`),
content: (
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={triggerIndex}
/>
),
})
),
{
title: t('Liquidity monitoring parameters'),
content: <LiquidityMonitoringParametersInfoPanel market={market} />,
},
{
title: t('Liquidity'),
content: (
<LiquidityInfoPanel market={market}>
<Link
to={`/liquidity/${market.id}`}
onClick={(ev) => onSelect?.(market.id, ev.metaKey || ev.ctrlKey)}
data-testid="view-liquidity-link"
>
<UILink>{t('View liquidity provision table')}</UILink>
</Link>
</LiquidityInfoPanel>
),
},
{
title: t('Liquidity price range'),
content: <LiquidityPriceRangeInfoPanel market={market} />,
},
];
const marketGovPanels = [
{
title: t('Proposal'),
content: (
<div className="">
{VEGA_TOKEN_URL && (
<ExternalLink
className="mb-2 w-full"
href={generatePath(TokenStaticLinks.PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
proposalId: market.proposal?.id || '',
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('View governance proposal')}
</ExternalLink>
)}
{VEGA_TOKEN_URL && (
<ExternalLink
className="w-full"
href={generatePath(TokenStaticLinks.UPDATE_PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('Propose a change to market')}
</ExternalLink>
)}
</div>
),
},
];
return ( return (
<div className="p-4"> <div className="p-4">
<div className="mb-8"> <div className="mb-8">
<h3 className={headerClassName}>{t('Market data')}</h3> <h3 className={headerClassName}>{t('Market data')}</h3>
<Accordion panels={marketDataPanels} /> <Accordion>
<AccordionItem
itemId="current-fees"
title={t('Current fees')}
content={<CurrentFeesInfoPanel market={market} />}
/>
<AccordionItem
itemId="market-price"
title={t('Market price')}
content={<MarketPriceInfoPanel market={market} />}
/>
<AccordionItem
itemId="market-volume"
title={t('Market volume')}
content={<MarketVolumeInfoPanel market={market} />}
/>
{marketAccounts
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a) => (
<AccordionItem
itemId={`${a.type}:${a.asset.id}`}
title={t('Insurance pool')}
content={<InsurancePoolInfoPanel market={market} account={a} />}
/>
))}
</Accordion>
</div> </div>
<div className="mb-8"> <div className="mb-8">
<MarketProposalNotification marketId={market.id} /> <MarketProposalNotification marketId={market.id} />
<h3 className={headerClassName}>{t('Market specification')}</h3> <h3 className={headerClassName}>{t('Market specification')}</h3>
<Accordion panels={marketSpecPanels} /> <Accordion>
<AccordionItem
itemId="key-details"
title={t('Key details')}
content={<KeyDetailsInfoPanel market={market} />}
/>
<AccordionItem
itemId="instrument"
title={t('Instrument')}
content={<InstrumentInfoPanel market={market} />}
/>
{isEqual(getSigners(settlementData), getSigners(terminationData)) ? (
<AccordionItem
itemId="oracles"
title={t('Oracle')}
content={
<OracleInfoPanel market={market} type="settlementData" />
}
/>
) : (
<>
<AccordionItem
itemId="settlement-oracle"
title={t('Settlement Oracle')}
content={
<OracleInfoPanel market={market} type="settlementData" />
}
/>
<AccordionItem
itemId="termination-oracle"
title={t('Termination Oracle')}
content={<OracleInfoPanel market={market} type="termination" />}
/>
</>
)}
<AccordionItem
itemId="settlement-asset"
title={t('Settlement asset')}
content={<SettlementAssetInfoPanel market={market} />}
/>
<AccordionItem
itemId="metadata"
title={t('Metadata')}
content={<MetadataInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-model"
title={t('Risk model')}
content={<RiskModelInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-parameters"
title={t('Risk parameters')}
content={<RiskParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-factors"
title={t('Risk factors')}
content={<RiskFactorsInfoPanel market={market} />}
/>
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(_, triggerIndex) => (
<AccordionItem
itemId={`trigger-${triggerIndex}`}
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
content={
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={triggerIndex}
/>
}
/>
)
)}
<AccordionItem
itemId="liqudity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
content={<LiquidityMonitoringParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity"
title={t('Liquidity')}
content={
<LiquidityInfoPanel market={market}>
<div className="mt-2">
<Link
to={`/liquidity/${market.id}`}
onClick={(ev) =>
onSelect?.(market.id, ev.metaKey || ev.ctrlKey)
}
data-testid="view-liquidity-link"
>
<UILink>{t('View liquidity provision table')}</UILink>
</Link>
</div>
</LiquidityInfoPanel>
}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={<LiquidityPriceRangeInfoPanel market={market} />}
/>
</Accordion>
</div> </div>
{VEGA_TOKEN_URL && marketGovPanels && market.proposal?.id && ( {VEGA_TOKEN_URL && market.proposal?.id && (
<div> <div>
<h3 className={headerClassName}>{t('Market governance')}</h3> <h3 className={headerClassName}>{t('Market governance')}</h3>
<Accordion panels={marketGovPanels} /> <Accordion>
<AccordionItem
itemId="proposal"
title={t('Proposal')}
content={
<>
<ExternalLink
className="mb-2 w-full"
href={generatePath(TokenStaticLinks.PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
proposalId: market.proposal?.id || '',
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('View governance proposal')}
</ExternalLink>
<ExternalLink
className="w-full"
href={generatePath(TokenStaticLinks.UPDATE_PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('Propose a change to market')}
</ExternalLink>
</>
}
/>
</Accordion>
</div> </div>
)} )}
</div> </div>

View File

@ -1,4 +1,4 @@
import type { ComponentProps } from 'react'; import type { ReactNode } from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets'; import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
@ -30,19 +30,12 @@ import { useOracleProofs } from '../../hooks';
import { OracleDialog } from '../oracle-dialog/oracle-dialog'; import { OracleDialog } from '../oracle-dialog/oracle-dialog';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
type PanelProps = Pick<
ComponentProps<typeof MarketInfoTable>,
'children' | 'noBorder'
>;
type MarketInfoProps = { type MarketInfoProps = {
market: MarketInfo; market: MarketInfo;
children?: ReactNode;
}; };
export const CurrentFeesInfoPanel = ({ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => (
market,
...props
}: MarketInfoProps & PanelProps) => (
<> <>
<MarketInfoTable <MarketInfoTable
data={{ data={{
@ -52,7 +45,6 @@ export const CurrentFeesInfoPanel = ({
totalFees: totalFeesPercentage(market.fees.factors), totalFees: totalFeesPercentage(market.fees.factors),
}} }}
asPercentage={true} asPercentage={true}
{...props}
/> />
<p className="text-xs"> <p className="text-xs">
{t( {t(
@ -62,10 +54,7 @@ export const CurrentFeesInfoPanel = ({
</> </>
); );
export const MarketPriceInfoPanel = ({ export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
const assetSymbol = const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || ''; market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit = const quoteUnit =
@ -84,9 +73,8 @@ export const MarketPriceInfoPanel = ({
quoteUnit: market.tradableInstrument.instrument.product.quoteName, quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}} }}
decimalPlaces={market.decimalPlaces} decimalPlaces={market.decimalPlaces}
{...props}
/> />
<p className="text-xs mt-4"> <p className="text-xs mt-2">
{t( {t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).', 'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit] [assetSymbol, quoteUnit]
@ -96,10 +84,7 @@ export const MarketPriceInfoPanel = ({
); );
}; };
export const MarketVolumeInfoPanel = ({ export const MarketVolumeInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
const { data } = useDataProvider({ const { data } = useDataProvider({
dataProvider: marketDataProvider, dataProvider: marketDataProvider,
variables: { marketId: market.id }, variables: { marketId: market.id },
@ -124,7 +109,6 @@ export const MarketVolumeInfoPanel = ({
bestStaticOfferVolume: dash(data?.bestStaticOfferVolume), bestStaticOfferVolume: dash(data?.bestStaticOfferVolume),
}} }}
decimalPlaces={market.positionDecimalPlaces} decimalPlaces={market.positionDecimalPlaces}
{...props}
/> />
); );
}; };
@ -132,13 +116,11 @@ export const MarketVolumeInfoPanel = ({
export const InsurancePoolInfoPanel = ({ export const InsurancePoolInfoPanel = ({
market, market,
account, account,
...props
}: { }: {
account: NonNullable< account: NonNullable<
Get<MarketInfoWithData, 'accountsConnection.edges[0].node'> Get<MarketInfoWithData, 'accountsConnection.edges[0].node'>
>; >;
} & MarketInfoProps & } & MarketInfoProps) => {
PanelProps) => {
const assetSymbol = const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || ''; market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
return ( return (
@ -150,14 +132,11 @@ export const InsurancePoolInfoPanel = ({
decimalPlaces={ decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset.decimals market.tradableInstrument.instrument.product.settlementAsset.decimals
} }
{...props}
/> />
); );
}; };
export const KeyDetailsInfoPanel = ({ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
market,
}: MarketInfoProps & PanelProps) => {
const assetDecimals = const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals; market.tradableInstrument.instrument.product.settlementAsset.decimals;
return ( return (
@ -175,10 +154,7 @@ export const KeyDetailsInfoPanel = ({
); );
}; };
export const InstrumentInfoPanel = ({ export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable <MarketInfoTable
data={{ data={{
marketName: market.tradableInstrument.instrument.name, marketName: market.tradableInstrument.instrument.name,
@ -186,14 +162,10 @@ export const InstrumentInfoPanel = ({
productType: market.tradableInstrument.instrument.product.__typename, productType: market.tradableInstrument.instrument.product.__typename,
quoteName: market.tradableInstrument.instrument.product.quoteName, quoteName: market.tradableInstrument.instrument.product.quoteName,
}} }}
{...props}
/> />
); );
export const SettlementAssetInfoPanel = ({ export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
market,
noBorder = true,
}: MarketInfoProps & PanelProps) => {
const assetSymbol = const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || ''; market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit = const quoteUnit =
@ -208,7 +180,7 @@ export const SettlementAssetInfoPanel = ({
<AssetDetailsTable <AssetDetailsTable
asset={asset} asset={asset}
inline={true} inline={true}
noBorder={noBorder} noBorder={true}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal" dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full" ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/> />
@ -224,10 +196,7 @@ export const SettlementAssetInfoPanel = ({
); );
}; };
export const MetadataInfoPanel = ({ export const MetadataInfoPanel = ({ market }: MarketInfoProps) => (
market,
...props
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable <MarketInfoTable
data={{ data={{
expiryDate: getMarketExpiryDateFormatted( expiryDate: getMarketExpiryDateFormatted(
@ -240,68 +209,44 @@ export const MetadataInfoPanel = ({
}) })
.reduce((acc, curr) => ({ ...acc, ...curr }), {}), .reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}} }}
{...props}
/> />
); );
export const RiskModelInfoPanel = ({ export const RiskModelInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') { if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') {
return null; return null;
} }
const { tau, riskAversionParameter } = market.tradableInstrument.riskModel; const { tau, riskAversionParameter } = market.tradableInstrument.riskModel;
return ( return <MarketInfoTable data={{ tau, riskAversionParameter }} unformatted />;
<MarketInfoTable
data={{ tau, riskAversionParameter }}
unformatted
{...props}
/>
);
}; };
export const RiskParametersInfoPanel = ({ export const RiskParametersInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') { if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') {
const { r, sigma, mu } = market.tradableInstrument.riskModel.params; const { r, sigma, mu } = market.tradableInstrument.riskModel.params;
return <MarketInfoTable data={{ r, sigma, mu }} unformatted {...props} />; return <MarketInfoTable data={{ r, sigma, mu }} unformatted />;
} }
if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') { if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') {
const { factorLong, factorShort } = const { factorLong, factorShort } =
market.tradableInstrument.riskModel.params; market.tradableInstrument.riskModel.params;
return ( return <MarketInfoTable data={{ factorLong, factorShort }} unformatted />;
<MarketInfoTable
data={{ factorLong, factorShort }}
unformatted
{...props}
/>
);
} }
return null; return null;
}; };
export const RiskFactorsInfoPanel = ({ export const RiskFactorsInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
if (!market.riskFactors) { if (!market.riskFactors) {
return null; return null;
} }
const { short, long } = market.riskFactors; const { short, long } = market.riskFactors;
return <MarketInfoTable data={{ short, long }} unformatted {...props} />; return <MarketInfoTable data={{ short, long }} unformatted />;
}; };
export const PriceMonitoringBoundsInfoPanel = ({ export const PriceMonitoringBoundsInfoPanel = ({
market, market,
triggerIndex, triggerIndex,
...props }: MarketInfoProps & {
}: {
triggerIndex: number; triggerIndex: number;
} & MarketInfoProps & }) => {
PanelProps) => {
const { data } = useDataProvider({ const { data } = useDataProvider({
dataProvider: marketDataProvider, dataProvider: marketDataProvider,
variables: { marketId: market.id }, variables: { marketId: market.id },
@ -318,8 +263,8 @@ export const PriceMonitoringBoundsInfoPanel = ({
return null; return null;
} }
return ( return (
<div className="text-xs"> <>
<div className="grid grid-cols-2 text-xs mb-4"> <div className="grid grid-cols-2 text-sm mb-2">
<p className="col-span-1"> <p className="col-span-1">
{t('%s probability price bounds', [ {t('%s probability price bounds', [
formatNumberPercentage( formatNumberPercentage(
@ -331,32 +276,28 @@ export const PriceMonitoringBoundsInfoPanel = ({
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])} {t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
</p> </p>
</div> </div>
<div className="pl-2 pb-0 text-xs border-l-2"> {bounds && (
{bounds && ( <MarketInfoTable
<MarketInfoTable data={{
data={{ highestPrice: bounds.maxValidPrice,
highestPrice: bounds.maxValidPrice, lowestPrice: bounds.minValidPrice,
lowestPrice: bounds.minValidPrice, }}
}} decimalPlaces={market.decimalPlaces}
decimalPlaces={market.decimalPlaces} assetSymbol={quoteUnit}
assetSymbol={quoteUnit} />
{...props} )}
/> <p className="text-xs mt-2">
)}
</div>
<p className="mt-4">
{t('Results in %s seconds auction if breached', [ {t('Results in %s seconds auction if breached', [
trigger.auctionExtensionSecs.toString(), trigger.auctionExtensionSecs.toString(),
])} ])}
</p> </p>
</div> </>
); );
}; };
export const LiquidityMonitoringParametersInfoPanel = ({ export const LiquidityMonitoringParametersInfoPanel = ({
market, market,
...props }: MarketInfoProps) => (
}: MarketInfoProps & PanelProps) => (
<MarketInfoTable <MarketInfoTable
data={{ data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio, triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
@ -366,14 +307,10 @@ export const LiquidityMonitoringParametersInfoPanel = ({
market.liquidityMonitoringParameters.targetStakeParameters market.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor, .scalingFactor,
}} }}
{...props}
/> />
); );
export const LiquidityInfoPanel = ({ export const LiquidityInfoPanel = ({ market, children }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
const assetDecimals = const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals; market.tradableInstrument.instrument.product.settlementAsset.decimals;
const assetSymbol = const assetSymbol =
@ -383,23 +320,22 @@ export const LiquidityInfoPanel = ({
variables: { marketId: market.id }, variables: { marketId: market.id },
}); });
return ( return (
<MarketInfoTable <>
data={{ <MarketInfoTable
targetStake: data?.targetStake, data={{
suppliedStake: data?.suppliedStake, targetStake: data?.targetStake,
marketValueProxy: data?.marketValueProxy, suppliedStake: data?.suppliedStake,
}} marketValueProxy: data?.marketValueProxy,
decimalPlaces={assetDecimals} }}
assetSymbol={assetSymbol} decimalPlaces={assetDecimals}
{...props} assetSymbol={assetSymbol}
/> />
{children}
</>
); );
}; };
export const LiquidityPriceRangeInfoPanel = ({ export const LiquidityPriceRangeInfoPanel = ({ market }: MarketInfoProps) => {
market,
...props
}: MarketInfoProps & PanelProps) => {
const quoteUnit = const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || ''; market?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage( const liquidityPriceRange = formatNumberPercentage(
@ -411,39 +347,37 @@ export const LiquidityPriceRangeInfoPanel = ({
}); });
return ( return (
<> <>
<p className="text-xs mb-4"> <p className="text-sm mb-2">
{`For liquidity orders to count towards a commitment, they must be {`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`} within the liquidity monitoring bounds.`}
</p> </p>
<p className="text-xs mb-4"> <p className="text-sm mb-2">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid {`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`} price.`}
</p> </p>
<div className="pl-2 pb-0 text-xs border-l-2"> <MarketInfoTable
<MarketInfoTable data={{
data={{ liquidityPriceRange: `${liquidityPriceRange} of mid price`,
liquidityPriceRange: `${liquidityPriceRange} of mid price`, lowestPrice:
lowestPrice: data?.midPrice &&
data?.midPrice && `${addDecimalsFormatNumber(
`${addDecimalsFormatNumber( new BigNumber(1)
new BigNumber(1) .minus(market.lpPriceRange)
.minus(market.lpPriceRange) .times(data.midPrice)
.times(data.midPrice) .toString(),
.toString(), market.decimalPlaces
market.decimalPlaces )} ${quoteUnit}`,
)} ${quoteUnit}`, highestPrice:
highestPrice: data?.midPrice &&
data?.midPrice && `${addDecimalsFormatNumber(
`${addDecimalsFormatNumber( new BigNumber(1)
new BigNumber(1) .plus(market.lpPriceRange)
.plus(market.lpPriceRange) .times(data.midPrice)
.times(data.midPrice) .toString(),
.toString(), market.decimalPlaces
market.decimalPlaces )} ${quoteUnit}`,
)} ${quoteUnit}`, }}
}} />
/>
</div>
</> </>
); );
}; };
@ -451,8 +385,7 @@ export const LiquidityPriceRangeInfoPanel = ({
export const OracleInfoPanel = ({ export const OracleInfoPanel = ({
market, market,
type, type,
}: MarketInfoProps & }: MarketInfoProps & { type: 'settlementData' | 'termination' }) => {
PanelProps & { type: 'settlementData' | 'termination' }) => {
const product = market.tradableInstrument.instrument.product; const product = market.tradableInstrument.instrument.product;
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);
@ -469,7 +402,7 @@ export const OracleInfoPanel = ({
) as DataSourceDefinition; ) as DataSourceDefinition;
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-2">
<DataSourceProof <DataSourceProof
data-testid="oracle-proof-links" data-testid="oracle-proof-links"
data={dataSourceSpec} data={dataSourceSpec}
@ -593,7 +526,7 @@ const OracleLink = ({
} }
return ( return (
<div> <div className="mt-2">
{signerProviders.map((provider) => ( {signerProviders.map((provider) => (
<OracleProfile <OracleProfile
key={dataSourceSpecId} key={dataSourceSpecId}

View File

@ -112,31 +112,27 @@ export const OracleBasicProfile = ({
<Icon size={3} name={icon as IconName} /> <Icon size={3} name={icon as IconName} />
</span> </span>
</span> </span>
<p className="dark:text-vega-light-300 text-vega-dark-300">{message}</p> <p className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2">
<p {message}
data-testid="signed-proofs" </p>
className="dark:text-vega-light-300 text-vega-dark-300" {oracleMarkets && (
> <p
{oracleMarkets && data-testid="signed-proofs"
t('Involved in %s %s', [ className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2"
>
{t('Involved in %s %s', [
oracleMarkets.length.toString(), oracleMarkets.length.toString(),
oracleMarkets.length !== 1 ? t('markets') : t('market'), oracleMarkets.length !== 1 ? t('markets') : t('market'),
])} ])}
</p> </p>
)}
{links.length > 0 && ( {links.length > 0 && (
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-3">
{links.map((link) => ( {links.map((link) => (
<ExternalLink <ExternalLink key={link.url} href={link.url} data-testid={link.url}>
key={link.url} <span className="flex gap-1 items-center">
href={link.url}
data-testid={link.url}
className="flex align-items-bottom underline text-sm"
>
<span className="pt-1 pr-1">
<VegaIcon name={getLinkIcon(link.type)} /> <VegaIcon name={getLinkIcon(link.type)} />
</span> <span className="capitalize underline">{link.type}</span>
<span className="underline capitalize">
{link.type}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} /> <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span> </span>
</ExternalLink> </ExternalLink>

View File

@ -1,15 +1,17 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import { Accordion } from './accordion'; import { Accordion, AccordionItem } from './accordion';
describe('Accordion', () => { describe('Accordion', () => {
it('should render successfully', () => { it('should render successfully', () => {
render( render(
<Accordion <Accordion>
panels={[ <AccordionItem
{ title: 'Lorem ipsum title', content: 'Lorem ipsum content' }, itemId="1"
]} title="Lorem ipsum title"
/> content="Lorem ipsum content"
/>
</Accordion>
); );
expect(screen.queryByTestId('accordion-title')).toHaveTextContent( expect(screen.queryByTestId('accordion-title')).toHaveTextContent(
'Lorem ipsum title' 'Lorem ipsum title'
@ -18,11 +20,13 @@ describe('Accordion', () => {
it('should toggle and open expansion panel', () => { it('should toggle and open expansion panel', () => {
render( render(
<Accordion <Accordion>
panels={[ <AccordionItem
{ title: 'Lorem ipsum title', content: 'Lorem ipsum content' }, itemId="1"
]} title="Lorem ipsum title"
/> content="Lorem ipsum content"
/>
</Accordion>
); );
fireEvent.click(screen.getByTestId('accordion-toggle')); fireEvent.click(screen.getByTestId('accordion-toggle'));
expect(screen.queryByTestId('accordion-title')).toHaveTextContent( expect(screen.queryByTestId('accordion-title')).toHaveTextContent(

View File

@ -1,5 +1,5 @@
import type { Story, Meta } from '@storybook/react'; import type { Story, Meta } from '@storybook/react';
import { Accordion } from './accordion'; import { Accordion, AccordionItem } from './accordion';
export default { export default {
component: Accordion, component: Accordion,
@ -10,18 +10,21 @@ const Template: Story = (args) => <Accordion panels={args.panels} />;
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
panels: [ children: [
{ <AccordionItem
title: 'Title of expansion panel', itemId="1"
content: 'Lorem ipsum', title={'Title of expansion panel'}
}, content={'Lorem ipsum'}
{ />,
title: 'Title of expansion panel', <AccordionItem
content: 'Lorem ipsum', itemId="2"
}, title={'Title of expansion panel'}
{ content={'Lorem ipsum'}
title: 'Title of expansion panel', />,
content: 'Lorem ipsum', <AccordionItem
}, itemId="3"
title={'Title of expansion panel'}
content={'Lorem ipsum'}
/>,
], ],
}; };

View File

@ -1,7 +1,6 @@
import React, { useState } from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion'; import * as AccordionPrimitive from '@radix-ui/react-accordion';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from '../icon'; import { VegaIcon, VegaIconNames } from '../icon';
export interface AccordionItemProps { export interface AccordionItemProps {
title: React.ReactNode; title: React.ReactNode;
@ -10,7 +9,6 @@ export interface AccordionItemProps {
export interface AccordionPanelProps extends AccordionItemProps { export interface AccordionPanelProps extends AccordionItemProps {
itemId: string; itemId: string;
active: boolean;
} }
export interface AccordionProps { export interface AccordionProps {
@ -19,23 +17,8 @@ export interface AccordionProps {
} }
export const Accordion = ({ panels, children }: AccordionProps) => { export const Accordion = ({ panels, children }: AccordionProps) => {
const [values, setValues] = useState<string[]>([]);
return ( return (
<AccordionPrimitive.Root <AccordionPrimitive.Root type="multiple">
type="multiple"
value={values}
onValueChange={setValues}
>
{panels?.map(({ title, content }, i) => (
<AccordionItem
key={`item-${i + 1}`}
itemId={`item-${i + 1}`}
title={title}
content={content}
active={values.includes(`item-${i + 1}`)}
/>
))}
{children} {children}
</AccordionPrimitive.Root> </AccordionPrimitive.Root>
); );
@ -45,11 +28,11 @@ export const AccordionItem = ({
title, title,
content, content,
itemId, itemId,
active,
}: AccordionPanelProps) => { }: AccordionPanelProps) => {
const triggerClassNames = classNames( const triggerClassNames = classNames(
'w-full py-2', 'w-full py-2',
'flex items-center justify-between border-b border-neutral-500 text-sm' 'flex items-center justify-between border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
'group'
); );
return ( return (
<AccordionPrimitive.Item value={itemId}> <AccordionPrimitive.Item value={itemId}>
@ -59,26 +42,28 @@ export const AccordionItem = ({
className={triggerClassNames} className={triggerClassNames}
> >
<span data-testid="accordion-title">{title}</span> <span data-testid="accordion-title">{title}</span>
<AccordionChevron active={active} aria-hidden /> <AccordionChevron aria-hidden />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
<AccordionPrimitive.Content data-testid="accordion-content-ref"> <AccordionPrimitive.Content
<div className="py-4 text-sm" data-testid="accordion-content"> className="py-3 text-sm"
{content} data-testid="accordion-content"
</div> >
{content}
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
</AccordionPrimitive.Item> </AccordionPrimitive.Item>
); );
}; };
export const AccordionChevron = ({ active }: { active: boolean }) => { export const AccordionChevron = () => {
return ( return (
<Icon <span
name="chevron-down" className={classNames(
className={classNames('transition ease-in-out duration-300', { 'transform transition ease-in-out duration-300',
'transform rotate-180': active, 'group-data-[state=open]:rotate-180'
})} )}
aria-hidden >
/> <VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden />
</span>
); );
}; };

View File

@ -43,8 +43,8 @@ Link.displayName = 'Link';
export const ExternalLink = ({ children, className, ...props }: LinkProps) => ( export const ExternalLink = ({ children, className, ...props }: LinkProps) => (
<Link <Link
className={classNames( className={classNames(
className, 'inline-flex items-center gap-1 underline-offset-4',
'inline-flex gap-1 items-baseline underline-offset-4' className
)} )}
target="_blank" target="_blank"
data-testid="external-link" data-testid="external-link"

View File

@ -42,7 +42,9 @@ export const Tooltip = ({
alignOffset={8} alignOffset={8}
className="max-w-sm border border-neutral-600 bg-neutral-100 dark:bg-neutral-800 px-4 py-2 z-20 rounded text-sm text-black dark:text-white break-word" className="max-w-sm border border-neutral-600 bg-neutral-100 dark:bg-neutral-800 px-4 py-2 z-20 rounded text-sm text-black dark:text-white break-word"
> >
<div className="relative z-0">{description}</div> <div className="relative z-0" data-testid="tooltip-content">
{description}
</div>
</Content> </Content>
</Portal> </Portal>
)} )}