feat(markets): succession line (#4491)
This commit is contained in:
parent
e3eb13ca72
commit
cdd91c24f2
@ -34,5 +34,8 @@ query SuccessorMarket($marketId: ID!) {
|
|||||||
code
|
code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
proposal {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -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={{
|
||||||
|
Loading…
Reference in New Issue
Block a user