Feat/707 link to market proposal (#1222)

* feat: link to market proposal (707)

* feat: link to market proposal (707)

* removed a line

* removed label which made edd and barney sad
This commit is contained in:
Art 2022-09-05 14:06:58 +02:00 committed by GitHub
parent 47554973c5
commit e674efdb14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 186 additions and 56 deletions

View File

@ -9,6 +9,7 @@ import Routes from '../../../routes';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Links } from '../../../../config'; import { Links } from '../../../../config';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
interface ProposalsListProps { interface ProposalsListProps {
proposals: Proposals_proposals[]; proposals: Proposals_proposals[];
@ -65,14 +66,9 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
{t( {t(
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network.` `The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network.`
)}{' '} )}{' '}
<a <ExternalLink href={Links.GOVERNANCE_PAGE} className="text-white">
href={Links.GOVERNANCE_PAGE}
className="underline text-white"
target="_blank"
rel="nofollow noreferrer"
>
{t(`Find out more about Vega governance`)} {t(`Find out more about Vega governance`)}
</a> </ExternalLink>
</p> </p>
</div> </div>
{proposals.length > 0 && ( {proposals.length > 0 && (

View File

@ -20,6 +20,15 @@ export const generateMarketInfoQuery = (
positionDecimalPlaces: 0, positionDecimalPlaces: 0,
state: MarketState.STATE_ACTIVE, state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
proposal: {
__typename: 'Proposal',
id: 'market-0',
rationale: {
__typename: 'ProposalRationale',
title: 'ETHBTC',
description: '',
},
},
accounts: [ accounts: [
{ {
type: AccountType.ACCOUNT_TYPE_INSURANCE, type: AccountType.ACCOUNT_TYPE_INSURANCE,

View File

@ -6,4 +6,5 @@ NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"} NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
NX_USE_ENV_OVERRIDES=1 NX_USE_ENV_OVERRIDES=1
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1 NX_VEGA_WALLET_URL=http://localhost:1789/api/v1

View File

@ -6,3 +6,4 @@ NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_TOKEN_URL=https://token.fairground.wtf

View File

@ -6,3 +6,4 @@ NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_TOKEN_URL=https://token.vega.xyz

View File

@ -6,3 +6,4 @@ NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://staging3.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://staging3.explorer.vega.xyz
NX_VEGA_TOKEN_URL=https://token.fairground.wtf

View File

@ -5,3 +5,4 @@ NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_TOKEN_URL=https://token.fairground.wtf

View File

@ -9,6 +9,34 @@ import { Interval, MarketState, AccountType, MarketTradingMode, AuctionTrigger }
// GraphQL query operation: MarketInfoQuery // GraphQL query operation: MarketInfoQuery
// ==================================================== // ====================================================
export interface MarketInfoQuery_market_proposal_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface MarketInfoQuery_market_proposal {
__typename: "Proposal";
/**
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: MarketInfoQuery_market_proposal_rationale;
}
export interface MarketInfoQuery_market_accounts_asset { export interface MarketInfoQuery_market_accounts_asset {
__typename: "Asset"; __typename: "Asset";
/** /**
@ -451,6 +479,10 @@ export interface MarketInfoQuery_market {
* Current state of the market * Current state of the market
*/ */
state: MarketState; state: MarketState;
/**
* The proposal that initiated this market
*/
proposal: MarketInfoQuery_market_proposal | null;
/** /**
* Get account for a party or market * Get account for a party or market
*/ */

View File

@ -8,6 +8,13 @@ export const MARKET_INFO_QUERY = gql`
decimalPlaces decimalPlaces
positionDecimalPlaces positionDecimalPlaces
state state
proposal {
id
rationale {
title
description
}
}
accounts { accounts {
type type
asset { asset {
@ -16,13 +23,6 @@ export const MARKET_INFO_QUERY = gql`
balance balance
} }
tradingMode tradingMode
accounts {
type
asset {
id
}
balance
}
fees { fees {
factors { factors {
makerFee makerFee
@ -44,13 +44,6 @@ export const MARKET_INFO_QUERY = gql`
short short
long long
} }
accounts {
type
asset {
id
}
balance
}
data { data {
market { market {
id id

View File

@ -26,6 +26,14 @@ import { useQuery } from '@apollo/client';
import { totalFees } from '@vegaprotocol/market-list'; import { totalFees } from '@vegaprotocol/market-list';
import { AccountType, Interval } from '@vegaprotocol/types'; import { AccountType, Interval } from '@vegaprotocol/types';
import { MARKET_INFO_QUERY } from './info-market-query'; import { MARKET_INFO_QUERY } from './info-market-query';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { generatePath } from 'react-router-dom';
import { useEnvironment } from '@vegaprotocol/environment';
const Links = {
PROPOSAL_PAGE: ':tokenUrl/governance/:proposalId',
};
export interface InfoProps { export interface InfoProps {
market: MarketInfoQuery_market; market: MarketInfoQuery_market;
@ -68,7 +76,8 @@ export const MarketInfoContainer = ({ marketId }: MarketInfoContainerProps) => {
}; };
export const Info = ({ market }: InfoProps) => { export const Info = ({ market }: InfoProps) => {
const headerClassName = 'uppercase text-lg mb-4'; const { VEGA_TOKEN_URL } = useEnvironment();
const headerClassName = 'uppercase text-lg';
const dayVolume = calcCandleVolume(market); const dayVolume = calcCandleVolume(market);
const marketDataPanels = [ const marketDataPanels = [
{ {
@ -272,16 +281,45 @@ export const Info = ({ market }: InfoProps) => {
}, },
]; ];
const marketGovPanels = [
{
title: t('Proposal'),
content: (
<p>
<ExternalLink
href={generatePath(Links.PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
proposalId: market.proposal?.id || '',
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('View governance proposal')}
</ExternalLink>
</p>
),
},
];
return ( return (
<div className="p-4"> <div className="p-4">
<div className="mb-4"> <div className="mb-4">
<p className={headerClassName}>{t('Market data')}</p> <p className={headerClassName}>{t('Market data')}</p>
<Accordion panels={marketDataPanels} /> <Accordion panels={marketDataPanels} />
</div> </div>
<div> <div className="mb-4">
<p className={headerClassName}>{t('Market specification')}</p> <p className={headerClassName}>{t('Market specification')}</p>
<Accordion panels={marketSpecPanels} /> <Accordion panels={marketSpecPanels} />
</div> </div>
{VEGA_TOKEN_URL && market.proposal?.id && (
<div>
<p className={headerClassName}>{t('Market governance')}</p>
<Accordion panels={marketGovPanels} />
</div>
)}
</div> </div>
); );
}; };

View File

@ -70,6 +70,8 @@ const getBundledEnvironmentValue = (key: EnvKey) => {
return process.env['NX_VEGA_EXPLORER_URL']; return process.env['NX_VEGA_EXPLORER_URL'];
case 'VEGA_WALLET_URL': case 'VEGA_WALLET_URL':
return process.env['NX_VEGA_WALLET_URL']; return process.env['NX_VEGA_WALLET_URL'];
case 'VEGA_TOKEN_URL':
return process.env['NX_VEGA_TOKEN_URL'];
} }
}; };

View File

@ -21,6 +21,7 @@ const schemaObject = {
GITHUB_FEEDBACK_URL: z.optional(z.string()), GITHUB_FEEDBACK_URL: z.optional(z.string()),
VEGA_ENV: z.nativeEnum(Networks), VEGA_ENV: z.nativeEnum(Networks),
VEGA_EXPLORER_URL: z.optional(z.string()), VEGA_EXPLORER_URL: z.optional(z.string()),
VEGA_TOKEN_URL: z.optional(z.string()),
VEGA_NETWORKS: z VEGA_NETWORKS: z
.object( .object(
Object.keys(Networks).reduce( Object.keys(Networks).reduce(

View File

@ -7,7 +7,7 @@ export type { IconName } from '@blueprintjs/icons';
export interface IconProps { export interface IconProps {
name: IconName; name: IconName;
className?: string; className?: string;
size?: 4 | 6 | 8 | 10 | 12 | 14 | 16; size?: 2 | 3 | 4 | 6 | 8 | 10 | 12 | 14 | 16;
ariaLabel?: string; ariaLabel?: string;
} }
@ -21,6 +21,8 @@ export const Icon = ({ size = 4, name, className, ariaLabel }: IconProps) => {
// Cant just concatenate as TW wont pick up that the class is being used // Cant just concatenate as TW wont pick up that the class is being used
// so below syntax is required // so below syntax is required
{ {
'w-2 h-2': size === 2,
'w-3 h-3': size === 3,
'w-4 h-4': size === 4, 'w-4 h-4': size === 4,
'w-6 h-6': size === 6, 'w-6 h-6': size === 6,
'w-8 h-8': size === 8, 'w-8 h-8': size === 8,

View File

@ -1 +1 @@
export { Link } from './link'; export * from './link';

View File

@ -1,7 +1,8 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { Link } from '.'; import { ExternalLink, Link } from '.';
it('renders a link with a text', () => { describe('Link', () => {
it('renders a link with a text', () => {
render( render(
<Link href="127.0.0.1" title="Link title"> <Link href="127.0.0.1" title="Link title">
Link text Link text
@ -15,9 +16,9 @@ it('renders a link with a text', () => {
expect(link).toHaveAttribute('title', 'Link title'); expect(link).toHaveAttribute('title', 'Link title');
expect(link).toHaveClass('cursor-pointer'); expect(link).toHaveClass('cursor-pointer');
expect(link).toHaveClass('underline'); expect(link).toHaveClass('underline');
}); });
it('renders a link with children elements', () => { it('renders a link with children elements', () => {
render( render(
<Link href="127.0.0.1" title="Link title"> <Link href="127.0.0.1" title="Link title">
<span>Link text</span> <span>Link text</span>
@ -31,4 +32,31 @@ it('renders a link with children elements', () => {
expect(link).toHaveAttribute('title', 'Link title'); expect(link).toHaveAttribute('title', 'Link title');
expect(link).toHaveClass('cursor-pointer'); expect(link).toHaveClass('cursor-pointer');
expect(link).not.toHaveClass('underline'); expect(link).not.toHaveClass('underline');
});
});
describe('ExternalLink', () => {
it('should have an icon indicating that it is an external link', () => {
render(<ExternalLink href="https://vega.xyz/">Go to Vega</ExternalLink>);
const link = screen.getByTestId('external-link');
expect(link.children.length).toEqual(2);
expect(link.children[1].tagName.toUpperCase()).toEqual('SVG');
});
it('should have an underlined text part', () => {
render(<ExternalLink href="https://vega.xyz/">Go to Vega</ExternalLink>);
const link = screen.getByTestId('external-link');
expect(link.children[0]).toHaveClass('underline');
});
it('should not have an icon or underlined text if wrapping an element', () => {
render(
<ExternalLink href="https://vega.xyz/">
<div className="inner">inner element</div>
</ExternalLink>
);
const link = screen.getByTestId('external-link');
expect(link.children.length).toEqual(1);
expect(link.children[0]).toHaveClass('inner');
});
}); });

View File

@ -1,5 +1,7 @@
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode, AnchorHTMLAttributes } from 'react'; import type { ReactNode, AnchorHTMLAttributes } from 'react';
import { Icon } from '../icon';
type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & { type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
children?: ReactNode; children?: ReactNode;
@ -27,5 +29,27 @@ export const Link = ({ className, children, ...props }: LinkProps) => {
</a> </a>
); );
}; };
Link.displayName = 'Link'; Link.displayName = 'Link';
export const ExternalLink = ({ children, className, ...props }: LinkProps) => (
<Link
className={classNames(className, 'inline-flex items-baseline')}
{...props}
target="_blank"
data-testid="external-link"
>
{typeof children === 'string' ? (
<>
<span
className={classNames({ underline: typeof children === 'string' })}
>
{children}
</span>
<Icon size={3} name={IconNames.SHARE} className="ml-1" />
</>
) : (
children
)}
</Link>
);
ExternalLink.displayName = 'ExternalLink';