feat(markets): succession line (#4491)

This commit is contained in:
Art 2023-08-07 13:46:03 +02:00 committed by GitHub
parent e3eb13ca72
commit cdd91c24f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 256 additions and 8 deletions

View File

@ -34,5 +34,8 @@ query SuccessorMarket($marketId: ID!) {
code code
} }
} }
proposal {
id
}
} }
} }

View File

@ -27,7 +27,7 @@ export type SuccessorMarketQueryVariables = Types.Exact<{
}>; }>;
export type SuccessorMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } | null }; export type SuccessorMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null } | null };
export const SuccessorMarketIdDocument = gql` export const SuccessorMarketIdDocument = gql`
@ -153,6 +153,9 @@ export const SuccessorMarketDocument = gql`
code code
} }
} }
proposal {
id
}
} }
} }
`; `;

View File

@ -1,4 +1,8 @@
import { TokenStaticLinks, useEnvironment } from '@vegaprotocol/environment'; import {
FLAGS,
TokenStaticLinks,
useEnvironment,
} from '@vegaprotocol/environment';
import { removePaginationWrapper } from '@vegaprotocol/utils'; import { removePaginationWrapper } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
@ -34,6 +38,7 @@ import {
RiskModelInfoPanel, RiskModelInfoPanel,
RiskParametersInfoPanel, RiskParametersInfoPanel,
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
SuccessionLineInfoPanel,
} from './market-info-panels'; } from './market-info-panels';
import type { DataSourceDefinition } from '@vegaprotocol/types'; import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
@ -291,6 +296,13 @@ export const MarketInfoAccordion = ({
</> </>
} }
/> />
{FLAGS.SUCCESSOR_MARKETS && (
<AccordionItem
itemId="succession-line"
title={t('Succession line')}
content={<SuccessionLineInfoPanel market={market} />}
/>
)}
</Accordion> </Accordion>
</div> </div>
)} )}

View File

@ -1,9 +1,11 @@
import { render, screen } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import { import {
ConditionOperator, ConditionOperator,
ConditionOperatorMapping, ConditionOperatorMapping,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { DataSourceProof } from './market-info-panels'; import { DataSourceProof, SuccessionLineInfoPanel } from './market-info-panels';
import { MockedProvider } from '@apollo/react-testing';
import { SuccessorMarketIdsDocument } from '../../__generated__';
jest.mock('../../hooks/use-oracle-markets', () => ({ jest.mock('../../hooks/use-oracle-markets', () => ({
useOracleMarkets: () => [], useOracleMarkets: () => [],
@ -137,3 +139,93 @@ describe('DataSourceProof', () => {
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
}); });
describe('SuccessionLineInfoPanel', () => {
const mocks = [
{
request: {
query: SuccessorMarketIdsDocument,
},
result: {
data: {
__typename: 'Query',
marketsConnection: {
__typename: 'MarketConnection',
edges: [
{
__typename: 'MarketEdge',
node: {
__typename: 'Market',
id: 'abc',
successorMarketID: 'def',
parentMarketID: null,
},
},
{
__typename: 'MarketEdge',
node: {
__typename: 'Market',
id: 'def',
successorMarketID: 'ghi',
parentMarketID: 'abc',
},
},
{
__typename: 'MarketEdge',
node: {
__typename: 'Market',
id: 'ghi',
successorMarketID: null,
parentMarketID: 'def',
},
},
],
},
},
},
},
];
it.each([
['abc', 1],
['def', 2],
['ghi', 3],
])(
'renders succession line for %s (current position %d)',
async (id, number) => {
render(
<MockedProvider mocks={mocks}>
<SuccessionLineInfoPanel
market={{
id,
}}
/>
</MockedProvider>
);
await waitFor(() => {
const items = screen.getAllByTestId('succession-line-item');
expect(items.length).toBe(3);
expect(
items[0].querySelector(
'[data-testid="succession-line-item-market-id"]'
)?.textContent
).toBe('abc');
expect(
items[1].querySelector(
'[data-testid="succession-line-item-market-id"]'
)?.textContent
).toBe('def');
expect(
items[2].querySelector(
'[data-testid="succession-line-item-market-id"]'
)?.textContent
).toBe('ghi');
expect(
items[number - 1].querySelector('[data-testid="icon-bullet"]')
).toBeInTheDocument();
});
}
);
});

View File

@ -1,11 +1,17 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useState } from 'react'; import { Fragment, useState } from 'react';
import { useMemo } from 'react'; 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 { marketDataProvider } from '../../market-data-provider'; import { marketDataProvider } from '../../market-data-provider';
import { totalFeesPercentage } from '../../market-utils'; import { totalFeesPercentage } from '../../market-utils';
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import {
ExternalLink,
Splash,
Tooltip,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
formatNumber, formatNumber,
@ -23,14 +29,26 @@ 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';
import { MarketTradingModeMapping } from '@vegaprotocol/types'; import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { FLAGS, useEnvironment } from '@vegaprotocol/environment'; import {
DApp,
FLAGS,
TOKEN_PROPOSAL,
useEnvironment,
useLinks,
} from '@vegaprotocol/environment';
import type { Provider } from '../../oracle-schema'; import type { Provider } from '../../oracle-schema';
import { OracleBasicProfile } from '../../components/oracle-basic-profile'; import { OracleBasicProfile } from '../../components/oracle-basic-profile';
import { useOracleProofs } from '../../hooks'; 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';
import { useParentMarketIdQuery } from '../../__generated__'; import {
useParentMarketIdQuery,
useSuccessorMarketIdsQuery,
useSuccessorMarketQuery,
} from '../../__generated__';
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals'; import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
import classNames from 'classnames';
import compact from 'lodash/compact';
type MarketInfoProps = { type MarketInfoProps = {
market: MarketInfo; market: MarketInfo;
@ -191,6 +209,126 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
); );
}; };
const SuccessionLineItem = ({
marketId,
isCurrent,
}: {
marketId: string;
isCurrent?: boolean;
}) => {
const { data } = useSuccessorMarketQuery({
variables: {
marketId,
},
});
const marketData = data?.market;
const governanceLink = useLinks(DApp.Token);
const proposalLink = marketData?.proposal?.id
? governanceLink(TOKEN_PROPOSAL.replace(':id', marketData?.proposal?.id))
: undefined;
return (
<div
data-testid="succession-line-item"
className={classNames(
'rounded p-2 bg-vega-clight-700 dark:bg-vega-cdark-700',
'font-alpha',
'flex flex-col '
)}
>
<div className="flex justify-between">
<div>
{marketData ? (
proposalLink ? (
<ExternalLink href={proposalLink}>
{marketData.tradableInstrument.instrument.code}
</ExternalLink>
) : (
marketData.tradableInstrument.instrument.code
)
) : (
<span className="block w-20 h-4 mb-1 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
)}
</div>
{isCurrent && (
<Tooltip description={t('This market')}>
<div className="text-vega-clight-200 dark:text-vega-cdark-200 cursor-help">
<VegaIcon name={VegaIconNames.BULLET} size={16} />
</div>
</Tooltip>
)}
</div>
<div className="text-xs">
{marketData ? (
marketData.tradableInstrument.instrument.name
) : (
<span className="block w-28 h-4 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
)}
</div>
<div
data-testid="succession-line-item-market-id"
className="text-xs truncate mt-1"
>
{marketId}
</div>
</div>
);
};
const SuccessionLink = () => (
<div className="text-center leading-none" aria-hidden>
<VegaIcon name={VegaIconNames.ARROW_DOWN} size={12} />
</div>
);
const buildSuccessionLine = (
all: {
id: string;
successorMarketID?: string | null | undefined;
parentMarketID?: string | null | undefined;
}[],
id: string
) => {
let line = [id];
const find = (id: string, dir?: 'up' | 'down') => {
const item = all.find((a) => a.id === id);
const anc = dir === 'up' && item?.parentMarketID;
const des = dir === 'down' && item?.successorMarketID;
if (anc) {
line = [anc, ...line];
find(anc, 'up');
}
if (des) {
line = [...line, des];
find(des, 'down');
}
};
find(id, 'up');
find(id, 'down');
return line;
};
export const SuccessionLineInfoPanel = ({
market,
}: {
market: Pick<MarketInfo, 'id'>;
}) => {
const { data } = useSuccessorMarketIdsQuery();
const ids = compact(data?.marketsConnection?.edges.map((e) => e.node));
const line = buildSuccessionLine(ids, market.id);
return (
<div className="flex flex-col gap-2">
{line.map((id, i) => (
<Fragment key={i}>
{i > 0 && <SuccessionLink />}
<SuccessionLineItem marketId={id} isCurrent={id === market.id} />
</Fragment>
))}
</div>
);
};
export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => ( export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
<MarketInfoTable <MarketInfoTable
data={{ data={{