diff --git a/libs/markets/src/lib/SuccessorMarket.graphql b/libs/markets/src/lib/SuccessorMarket.graphql index 4f5255987..53c661c91 100644 --- a/libs/markets/src/lib/SuccessorMarket.graphql +++ b/libs/markets/src/lib/SuccessorMarket.graphql @@ -34,5 +34,8 @@ query SuccessorMarket($marketId: ID!) { code } } + proposal { + id + } } } diff --git a/libs/markets/src/lib/__generated__/SuccessorMarket.ts b/libs/markets/src/lib/__generated__/SuccessorMarket.ts index 82bb761c3..818441a39 100644 --- a/libs/markets/src/lib/__generated__/SuccessorMarket.ts +++ b/libs/markets/src/lib/__generated__/SuccessorMarket.ts @@ -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` @@ -153,6 +153,9 @@ export const SuccessorMarketDocument = gql` code } } + proposal { + id + } } } `; diff --git a/libs/markets/src/lib/components/market-info/market-info-accordion.tsx b/libs/markets/src/lib/components/market-info/market-info-accordion.tsx index 17e835e96..3d91df600 100644 --- a/libs/markets/src/lib/components/market-info/market-info-accordion.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-accordion.tsx @@ -1,4 +1,8 @@ -import { TokenStaticLinks, useEnvironment } from '@vegaprotocol/environment'; +import { + FLAGS, + TokenStaticLinks, + useEnvironment, +} from '@vegaprotocol/environment'; import { removePaginationWrapper } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { useDataProvider } from '@vegaprotocol/data-provider'; @@ -34,6 +38,7 @@ import { RiskModelInfoPanel, RiskParametersInfoPanel, SettlementAssetInfoPanel, + SuccessionLineInfoPanel, } from './market-info-panels'; import type { DataSourceDefinition } from '@vegaprotocol/types'; import isEqual from 'lodash/isEqual'; @@ -291,6 +296,13 @@ export const MarketInfoAccordion = ({ } /> + {FLAGS.SUCCESSOR_MARKETS && ( + } + /> + )} )} diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.spec.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.spec.tsx index b13e7c6be..98d8aa390 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.spec.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.spec.tsx @@ -1,9 +1,11 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { ConditionOperator, ConditionOperatorMapping, } 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', () => ({ useOracleMarkets: () => [], @@ -137,3 +139,93 @@ describe('DataSourceProof', () => { ).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( + + + + ); + + 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(); + }); + } + ); +}); diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.tsx index abfb16f7a..001f562c6 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.tsx @@ -1,11 +1,17 @@ import type { ReactNode } from 'react'; -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import { useMemo } from 'react'; import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets'; import { t } from '@vegaprotocol/i18n'; import { marketDataProvider } from '../../market-data-provider'; import { totalFeesPercentage } from '../../market-utils'; -import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; +import { + ExternalLink, + Splash, + Tooltip, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; import { addDecimalsFormatNumber, formatNumber, @@ -23,14 +29,26 @@ import BigNumber from 'bignumber.js'; import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types'; import { ConditionOperatorMapping } 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 { OracleBasicProfile } from '../../components/oracle-basic-profile'; import { useOracleProofs } from '../../hooks'; import { OracleDialog } from '../oracle-dialog/oracle-dialog'; import { useDataProvider } from '@vegaprotocol/data-provider'; -import { useParentMarketIdQuery } from '../../__generated__'; +import { + useParentMarketIdQuery, + useSuccessorMarketIdsQuery, + useSuccessorMarketQuery, +} from '../../__generated__'; import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals'; +import classNames from 'classnames'; +import compact from 'lodash/compact'; type MarketInfoProps = { 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 ( +
+
+
+ {marketData ? ( + proposalLink ? ( + + {marketData.tradableInstrument.instrument.code} + + ) : ( + marketData.tradableInstrument.instrument.code + ) + ) : ( + + )} +
+ {isCurrent && ( + +
+ +
+
+ )} +
+
+ {marketData ? ( + marketData.tradableInstrument.instrument.name + ) : ( + + )} +
+
+ {marketId} +
+
+ ); +}; + +const SuccessionLink = () => ( +
+ +
+); + +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; +}) => { + const { data } = useSuccessorMarketIdsQuery(); + const ids = compact(data?.marketsConnection?.edges.map((e) => e.node)); + const line = buildSuccessionLine(ids, market.id); + + return ( +
+ {line.map((id, i) => ( + + {i > 0 && } + + + ))} +
+ ); +}; + export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (