Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ffce2f8c9 | ||
|
|
35e9affc83 | ||
|
|
fe02d924d5 | ||
|
|
c531763eae | ||
|
|
cae5b45a9f | ||
|
|
f257d7aa78 | ||
|
|
73cc101751 | ||
|
|
34c19d9f84 | ||
|
|
2a1adfa004 | ||
|
|
4b9e63e1ee | ||
|
|
c0bdf9e862 | ||
|
|
af6719cc9d | ||
|
|
c433a4ee06 | ||
|
|
e3a00ada05 | ||
|
|
aa38e0c001 | ||
|
|
86da521da5 | ||
|
|
c6df34fe95 | ||
|
|
c7d0803164 | ||
|
|
250a654544 | ||
|
|
7f5e8ebb15 | ||
|
|
ccbe34e172 | ||
|
|
27e8ce88bf | ||
|
|
41804d9869 | ||
|
|
293288286b | ||
|
|
a77765b1e9 |
@@ -1,6 +1,6 @@
|
||||
# Related issues 🔗
|
||||
|
||||
Issue: #[Issue number here]
|
||||
Closes #[Issue number here]
|
||||
|
||||
# Description ℹ️
|
||||
|
||||
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
* @vegaprotocol/frontend @vegaprotocol/frontend-qa
|
||||
* @vegaprotocol/frontend
|
||||
* @vegaprotocol/frontend-qa
|
||||
*.graphql @vegaprotocol/core
|
||||
|
||||
@@ -19,7 +19,22 @@ export const upgradeProposalsData = {
|
||||
},
|
||||
{
|
||||
node: {
|
||||
upgradeBlockHeight: '1955065',
|
||||
upgradeBlockHeight: '10001',
|
||||
vegaReleaseTag: 'v0.71.0+dev-12156-bca1d57e',
|
||||
approvers: [
|
||||
'02a6531716b7a6d82779b7793c3ad2fcb47290ea2ff0912c56f61219bd9675ff',
|
||||
'121934387281812a2d5e6913e5d57c0f85a8f169e2752347ee2e23b52d46623c',
|
||||
'65c80e2f5f84e2109eec30810f137ba04cbbecaba8f27706c146cc6c6f90db29',
|
||||
'bd6339d2428c79ac3bc9011771236d17bac92bcb1806423388d52fb440043aef',
|
||||
],
|
||||
status: 'PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED',
|
||||
__typename: 'ProtocolUpgradeProposal',
|
||||
},
|
||||
__typename: 'ProtocolUpgradeProposalEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
upgradeBlockHeight: '20',
|
||||
vegaReleaseTag: 'v0.71.0+dev-12156-bca1d57e',
|
||||
approvers: [
|
||||
'02a6531716b7a6d82779b7793c3ad2fcb47290ea2ff0912c56f61219bd9675ff',
|
||||
|
||||
@@ -262,10 +262,10 @@ context(
|
||||
'2'
|
||||
);
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId(stakeValidatorListStakePercentage).should(
|
||||
'have.text',
|
||||
'50.02%'
|
||||
);
|
||||
cy.getByTestId(
|
||||
stakeValidatorListStakePercentage,
|
||||
epochTimeout
|
||||
).should('have.text', '50.02%');
|
||||
navigateTo(navigation.validators);
|
||||
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const proposalDocumentationLink = 'proposal-documentation-link';
|
||||
const connectToVegaWalletButton = 'connect-to-vega-wallet-btn';
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const networkUpgradeProposalListItem = 'protocol-upgrade-proposals-list-item';
|
||||
const proposalUpgradeBlockHeight = 'protocol-upgrade-proposal-block-height';
|
||||
const closedProposals = 'closed-proposals';
|
||||
const closedProposalToggle = 'closed-proposals-toggle-networkUpgrades';
|
||||
const protocolUpgradeTime = 'protocol-upgrade-time';
|
||||
@@ -184,7 +185,7 @@ context(
|
||||
'have.text',
|
||||
'Vega release tag: v1'
|
||||
);
|
||||
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
|
||||
cy.getByTestId(proposalUpgradeBlockHeight).should(
|
||||
'have.text',
|
||||
'Upgrade block height: 2015942'
|
||||
);
|
||||
@@ -199,7 +200,15 @@ context(
|
||||
});
|
||||
cy.getByTestId(closedProposalToggle).click();
|
||||
cy.getByTestId(closedProposals).within(() => {
|
||||
cy.getByTestId(networkUpgradeProposalListItem).should('have.length', 1);
|
||||
cy.getByTestId(networkUpgradeProposalListItem).should('have.length', 2);
|
||||
cy.getByTestId(networkUpgradeProposalListItem)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId(proposalUpgradeBlockHeight).should(
|
||||
'contain.text',
|
||||
'10001'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -170,21 +170,20 @@ export function clickOnValidatorFromList(
|
||||
validatorName = null
|
||||
) {
|
||||
cy.contains('Loading...', epochTimeout).should('not.exist');
|
||||
waitForBeginningOfEpoch();
|
||||
// below is to ensure validator list is shown
|
||||
cy.get(stakeValidatorListName, { timeout: 10000 }).should('exist');
|
||||
cy.get(stakeValidatorListPendingStake, txTimeout).should(
|
||||
'not.contain',
|
||||
'2,000,000,000,000,000,000.00' // number due to bug #936
|
||||
);
|
||||
waitForBeginningOfEpoch();
|
||||
if (validatorName) {
|
||||
cy.contains(validatorName).click();
|
||||
} else {
|
||||
cy.get(`[row-id="${validatorNumber}"]`)
|
||||
.should('be.visible')
|
||||
.first()
|
||||
.as('validatorOnList');
|
||||
cy.get('@validatorOnList').click();
|
||||
.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +195,7 @@ export function validateValidatorListTotalStakeAndShare(
|
||||
cy.contains('Loading...', epochTimeout).should('not.exist');
|
||||
waitForBeginningOfEpoch();
|
||||
cy.get(`[row-id="${positionOnList}"]`)
|
||||
.eq(1)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId(stakeValidatorListTotalStake, epochTimeout).should(
|
||||
'have.text',
|
||||
|
||||
@@ -45,7 +45,7 @@ const HomeProposals = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section className="mb-16" data-testid="home-proposals">
|
||||
<section className="mb-16 break-all" data-testid="home-proposals">
|
||||
<Heading title={t('vegaGovernance')} />
|
||||
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
|
||||
<div className="mb-8">
|
||||
|
||||
@@ -127,7 +127,7 @@ export const Proposal = ({
|
||||
voteState={voteState}
|
||||
/>
|
||||
|
||||
<div className="my-10">
|
||||
<div className="my-10 break-all">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
|
||||
-2
@@ -352,7 +352,6 @@ export const ConsensusValidatorsTable = ({
|
||||
field: ValidatorFields.RANKING_INDEX,
|
||||
headerName: '#',
|
||||
width: 60,
|
||||
pinned: 'left',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.VALIDATOR,
|
||||
@@ -362,7 +361,6 @@ export const ConsensusValidatorsTable = ({
|
||||
if (a === b) return 0;
|
||||
return a > b ? 1 : -1;
|
||||
},
|
||||
pinned: 'left',
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
|
||||
-2
@@ -195,14 +195,12 @@ export const StandbyPendingValidatorsTable = ({
|
||||
field: ValidatorFields.RANKING_INDEX,
|
||||
headerName: '#',
|
||||
width: 60,
|
||||
pinned: 'left',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.VALIDATOR,
|
||||
headerName: t(ValidatorFields.VALIDATOR).toString(),
|
||||
cellRenderer: ValidatorRenderer,
|
||||
comparator: ({ name: a }, { name: b }) => Math.sign(a - b),
|
||||
pinned: 'left',
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -122,38 +122,43 @@ export const ValidatorTable = ({
|
||||
|
||||
<div className="my-12" data-testid="validator-table">
|
||||
<SubHeading title={t('profile')} />
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-profile">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('id')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-id">
|
||||
{node.id}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ABOUT THIS VALIDATOR')}</span>
|
||||
<div className="break-all">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-profile">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('id')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-id">
|
||||
{node.id}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('ABOUT THIS VALIDATOR')}</span>
|
||||
|
||||
<Tooltip description={t('AboutThisValidatorDescription')}>
|
||||
<a data-testid="validator-description-url" href={node.infoUrl}>
|
||||
{node.infoUrl}
|
||||
</a>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('STATUS')}</strong>
|
||||
</span>
|
||||
|
||||
<Tooltip description={t('ValidatorStatusDescription')}>
|
||||
<span data-testid="validator-status">
|
||||
<strong>
|
||||
{t(statusTranslationKey(node.rankingScore.status))}
|
||||
</strong>
|
||||
<Tooltip description={t('AboutThisValidatorDescription')}>
|
||||
<a
|
||||
data-testid="validator-description-url"
|
||||
href={node.infoUrl}
|
||||
>
|
||||
{node.infoUrl}
|
||||
</a>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('STATUS')}</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
|
||||
<Tooltip description={t('ValidatorStatusDescription')}>
|
||||
<span data-testid="validator-status">
|
||||
<strong>
|
||||
{t(statusTranslationKey(node.rankingScore.status))}
|
||||
</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<div className="mb-10">
|
||||
{t('validatorTableIntro')}{' '}
|
||||
@@ -167,144 +172,154 @@ export const ValidatorTable = ({
|
||||
</div>
|
||||
|
||||
<SubHeading title={t('ADDRESS')} />
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-address">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-public-key">
|
||||
{node.pubkey}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('SERVER LOCATION')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-server-location">
|
||||
{countryData.find((c) => c.code === node.location)?.name ||
|
||||
t('not available')}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>{t('ETHEREUM ADDRESS')}</span>
|
||||
<span data-testid="validator-eth-address">
|
||||
<Link
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
|
||||
target="_blank"
|
||||
>
|
||||
{node.ethereumAddress}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
<div className="break-all">
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-address">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-public-key">
|
||||
{node.pubkey}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('SERVER LOCATION')}</span>
|
||||
<ValidatorTableCell dataTestId="validator-server-location">
|
||||
{countryData.find((c) => c.code === node.location)?.name ||
|
||||
t('not available')}
|
||||
</ValidatorTableCell>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>{t('ETHEREUM ADDRESS')}</span>
|
||||
<span data-testid="validator-eth-address">
|
||||
<Link
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
|
||||
target="_blank"
|
||||
>
|
||||
{node.ethereumAddress}
|
||||
</Link>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<SubHeading title={t('STAKE')} />
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-stake">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY OPERATOR')}</span>
|
||||
<div className="break-all">
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-stake">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY OPERATOR')}</span>
|
||||
|
||||
<Tooltip description={t('StakedByOperatorDescription')}>
|
||||
<span data-testid="staked-by-operator">
|
||||
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
|
||||
<Tooltip description={t('StakedByOperatorDescription')}>
|
||||
<span data-testid="staked-by-operator">
|
||||
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY DELEGATES')}</span>
|
||||
|
||||
<Tooltip description={t('StakedByDelegatesDescription')}>
|
||||
<span data-testid="staked-by-delegates">
|
||||
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('TOTAL STAKE')}</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('STAKED BY DELEGATES')}</span>
|
||||
|
||||
<Tooltip description={t('StakedByDelegatesDescription')}>
|
||||
<span data-testid="staked-by-delegates">
|
||||
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
|
||||
<span data-testid="total-stake">
|
||||
<strong>
|
||||
{formatNumber(toBigNum(node.stakedTotal, decimals))}
|
||||
</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>
|
||||
<strong>{t('TOTAL STAKE')}</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PENDING STAKE')}</span>
|
||||
|
||||
<span data-testid="total-stake">
|
||||
<strong>
|
||||
{formatNumber(toBigNum(node.stakedTotal, decimals))}
|
||||
</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PENDING STAKE')}</span>
|
||||
<Tooltip description={t('PendingStakeDescription')}>
|
||||
<span data-testid="pending-stake">
|
||||
{formatNumber(toBigNum(node.pendingStake, decimals))}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>{t('STAKE SHARE')}</span>
|
||||
|
||||
<Tooltip description={t('PendingStakeDescription')}>
|
||||
<span data-testid="pending-stake">
|
||||
{formatNumber(toBigNum(node.pendingStake, decimals))}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>{t('STAKE SHARE')}</span>
|
||||
|
||||
<Tooltip description={t('StakeShareDescription')}>
|
||||
<span data-testid="stake-percentage">{stakePercentage}</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
<Tooltip description={t('StakeShareDescription')}>
|
||||
<span data-testid="stake-percentage">{stakePercentage}</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<SubHeading title={t('PENALTIES')} />
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-penalties">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('OVERSTAKED PENALTY')}</span>
|
||||
<div className="break-all">
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-penalties">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('OVERSTAKED PENALTY')}</span>
|
||||
|
||||
<Tooltip description={t('OverstakedPenaltyDescription')}>
|
||||
<span data-testid="overstaking-penalty">
|
||||
{formatNumberPercentage(penalties.overstaked, 2)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PERFORMANCE PENALTY')}</span>
|
||||
<Tooltip description={t('OverstakedPenaltyDescription')}>
|
||||
<span data-testid="overstaking-penalty">
|
||||
{formatNumberPercentage(penalties.overstaked, 2)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('PERFORMANCE PENALTY')}</span>
|
||||
|
||||
<Tooltip description={t('PerformancePenaltyDescription')}>
|
||||
<span data-testid="performance-penalty">
|
||||
{formatNumberPercentage(penalties.performance, 2)}
|
||||
<Tooltip description={t('PerformancePenaltyDescription')}>
|
||||
<span data-testid="performance-penalty">
|
||||
{formatNumberPercentage(penalties.performance, 2)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('TOTAL PENALTIES')}</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('TOTAL PENALTIES')}</strong>
|
||||
</span>
|
||||
<span data-testid="total-penalties">
|
||||
<strong>{formatNumberPercentage(penalties.overall, 2)}</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
<span data-testid="total-penalties">
|
||||
<strong>
|
||||
{formatNumberPercentage(penalties.overall, 2)}
|
||||
</strong>
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<SubHeading title={t('VOTING POWER')} />
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-voting-power">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('UNNORMALISED VOTING POWER')}</span>
|
||||
<div className="break-all">
|
||||
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||
<KeyValueTable data-testid="validator-table-voting-power">
|
||||
<KeyValueTableRow>
|
||||
<span>{t('UNNORMALISED VOTING POWER')}</span>
|
||||
|
||||
<Tooltip description={t('UnnormalisedVotingPowerDescription')}>
|
||||
<span data-testid="unnormalised-voting-power">
|
||||
{getUnnormalisedVotingPower(rawValidatorScore)}
|
||||
<Tooltip description={t('UnnormalisedVotingPowerDescription')}>
|
||||
<span data-testid="unnormalised-voting-power">
|
||||
{getUnnormalisedVotingPower(rawValidatorScore)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('NORMALISED VOTING POWER')}</strong>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow noBorder={true}>
|
||||
<span>
|
||||
<strong>{t('NORMALISED VOTING POWER')}</strong>
|
||||
</span>
|
||||
|
||||
<Tooltip description={t('NormalisedVotingPowerDescription')}>
|
||||
<strong data-testid="normalised-voting-power">
|
||||
{getNormalisedVotingPower(node.rankingScore.votingPower)}
|
||||
</strong>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
<Tooltip description={t('NormalisedVotingPowerDescription')}>
|
||||
<strong data-testid="normalised-voting-power">
|
||||
{getNormalisedVotingPower(node.rankingScore.votingPower)}
|
||||
</strong>
|
||||
</Tooltip>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ export const TokenDetails = ({
|
||||
config.token_vesting_contract?.address || ENV.addresses.tokenVestingAddress;
|
||||
|
||||
return (
|
||||
<div className="token-details">
|
||||
<div className="token-details break-all">
|
||||
<RoundedWrapper>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
|
||||
+12
-11
@@ -1,27 +1,28 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -16,14 +16,14 @@ NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.21.0-core-0.72.14
|
||||
NX_APP_VERSION=v0.21.1-core-0.72.14
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -22,7 +22,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { marketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
|
||||
// The home pages only purpose is to redirect to the users last market,
|
||||
// the top traded if they are new, or fall back to the list of markets.
|
||||
// Thats why we just render a loader here
|
||||
export const Home = () => {
|
||||
const navigate = useNavigate();
|
||||
// The default market selected in the platform behind the overlay
|
||||
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider: marketsWithDataProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const { data } = useTopTradedMarkets();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,12 +28,11 @@ export const Home = () => {
|
||||
navigate(Links[Routes.MARKETS]());
|
||||
}
|
||||
}
|
||||
}, [marketId, data, navigate, update]);
|
||||
}, [marketId, data, navigate]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer data={data} loading={loading} error={error}>
|
||||
{/* Render a loading and error state but we will redirect if markets are found */}
|
||||
{null}
|
||||
</AsyncRenderer>
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -69,15 +69,15 @@ export const MarketPage = () => {
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId) {
|
||||
update({ marketId: data.id });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
// Make sidebar open on deal ticket by default
|
||||
useEffect(() => {
|
||||
if (init && view === null) {
|
||||
// make sidebar open on market id change
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [init, view, setView]);
|
||||
|
||||
// make sidebar open on deal ticket by default
|
||||
if (view === null) {
|
||||
setView({ type: ViewType.Order });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id, setView, init, view]);
|
||||
|
||||
const tradeView = useMemo(() => {
|
||||
if (largeScreen) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { act, render, screen, within, waitFor } from '@testing-library/react';
|
||||
import { act, render, screen, within } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Closed } from './closed';
|
||||
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
|
||||
@@ -10,55 +10,21 @@ import type {
|
||||
OracleSpecDataConnectionQuery,
|
||||
MarketsDataQuery,
|
||||
MarketsQuery,
|
||||
SuccessorMarketIdsQuery,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
OracleSpecDataConnectionDocument,
|
||||
MarketsDataDocument,
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
marketsDataQuery,
|
||||
createMarketsDataFragment,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { FeatureFlags } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
...jest.requireActual('@vegaprotocol/markets'),
|
||||
useSuccessorMarket: (marketId: string) =>
|
||||
marketId === 'include-0'
|
||||
? {
|
||||
data: {
|
||||
id: 'successorMarketID',
|
||||
state: 'STATE_ACTIVE',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: { data: undefined },
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
} as FeatureFlags,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Closed', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
@@ -218,10 +184,8 @@ describe('Closed', () => {
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Status',
|
||||
'Settlement date',
|
||||
'Successor market',
|
||||
'Best bid',
|
||||
'Best offer',
|
||||
'Mark price',
|
||||
@@ -235,10 +199,8 @@ describe('Closed', () => {
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
market.tradableInstrument.instrument.code,
|
||||
market.tradableInstrument.instrument.name,
|
||||
MarketStateMapping[market.state],
|
||||
'3 days ago',
|
||||
'-',
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
addDecimalsFormatNumber(marketsData.bestBidPrice, market.decimalPlaces),
|
||||
addDecimalsFormatNumber(
|
||||
@@ -340,43 +302,26 @@ describe('Closed', () => {
|
||||
.getAllByRole('gridcell')
|
||||
.filter((cell) => cell.getAttribute('col-id') === 'code')
|
||||
.map((cell) => {
|
||||
const marketId = within(cell)
|
||||
.getByTestId('market-code')
|
||||
.getAttribute('data-market-id');
|
||||
return marketId;
|
||||
const marketCode = within(cell).getByTestId('stack-cell-primary');
|
||||
return marketCode.textContent;
|
||||
});
|
||||
expect(cells).toEqual(expectedRows.map((m) => m.node.id));
|
||||
expect(cells).toEqual(
|
||||
expectedRows.map((m) => m.node.tradableInstrument.instrument.code)
|
||||
);
|
||||
});
|
||||
|
||||
it('successor marked should be visible', async () => {
|
||||
const mixedMarkets = [
|
||||
const marketsWithSuccessorID = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
successorMarketID: 'successor',
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
const mockWithSuccessors: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
@@ -384,42 +329,17 @@ describe('Closed', () => {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: mixedMarkets,
|
||||
edges: marketsWithSuccessorID,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
mocks={[mockWithSuccessors, marketsDataMock, oracleDataMock]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
@@ -431,122 +351,15 @@ describe('Closed', () => {
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('button', { name: /^SuccessorCode/ })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
screen
|
||||
.getAllByRole('gridcell', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
.forEach((element) => {
|
||||
expect(element.querySelector('[title="Future"]')?.textContent).toEqual(
|
||||
'Futr'
|
||||
);
|
||||
});
|
||||
});
|
||||
const container = within(
|
||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||
);
|
||||
const cell = container.getAllByRole('gridcell', {
|
||||
name: (_name, element) => element.getAttribute('col-id') === 'code',
|
||||
})[0];
|
||||
|
||||
it('feature flag should hide successors', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
|
||||
const mixedMarkets = [
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: createMarketFragment({
|
||||
id: 'include-0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
}),
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge' as const,
|
||||
node: {
|
||||
...createMarketFragment({
|
||||
id: 'successorMarketID',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
}),
|
||||
tradableInstrument: {
|
||||
...createMarketFragment().tradableInstrument,
|
||||
instrument: {
|
||||
...createMarketFragment().tradableInstrument.instrument,
|
||||
id: 'successorAssset',
|
||||
name: 'Successor Market Name',
|
||||
code: 'SuccessorCode',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: mixedMarkets,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'include-0',
|
||||
successorMarketID: 'successorMarketID',
|
||||
parentMarketID: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
>
|
||||
<Closed />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'settlementDate',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('successorMarket');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,10 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
COL_DEFS,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { ProductType } from '@vegaprotocol/types';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
@@ -20,17 +17,13 @@ import type {
|
||||
DataSourceFilterFragment,
|
||||
MarketMaybeWithData,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
MarketActionsDropdown,
|
||||
closedMarketsWithDataProvider,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { closedMarketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
type SettlementAsset =
|
||||
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||
@@ -51,7 +44,9 @@ interface Row {
|
||||
setlementDataSourceFilter: DataSourceFilterFragment | undefined;
|
||||
tradingTerminationOracleId: string;
|
||||
settlementAsset: SettlementAsset;
|
||||
productType: string;
|
||||
productType: ProductType | undefined;
|
||||
successorMarketID: string | null | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
}
|
||||
|
||||
export const Closed = () => {
|
||||
@@ -95,16 +90,19 @@ export const Closed = () => {
|
||||
tradingTerminationOracleId:
|
||||
instrument.product.dataSourceSpecForTradingTermination.id,
|
||||
settlementAsset: instrument.product.settlementAsset,
|
||||
productType: instrument.product.__typename || '',
|
||||
productType: instrument.product.__typename,
|
||||
successorMarketID: market.successorMarketID,
|
||||
parentMarketID: market.parentMarketID,
|
||||
};
|
||||
|
||||
return row;
|
||||
});
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<ClosedMarketsDataGrid rowData={rowData} error={error} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return <ClosedMarketsDataGrid rowData={rowData} error={error} />;
|
||||
};
|
||||
|
||||
const components = {
|
||||
MarketCodeCell,
|
||||
};
|
||||
|
||||
const ClosedMarketsDataGrid = ({
|
||||
@@ -117,15 +115,11 @@ const ClosedMarketsDataGrid = ({
|
||||
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const cols: ColDef[] = compact([
|
||||
return [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'name',
|
||||
cellRenderer: 'MarketCodeCell',
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
@@ -176,12 +170,6 @@ const ClosedMarketsDataGrid = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
field: 'id',
|
||||
colId: 'successorMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
headerName: t('Best bid'),
|
||||
field: 'bestBidPrice',
|
||||
@@ -263,12 +251,13 @@ const ClosedMarketsDataGrid = ({
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={data.settlementAsset.id}
|
||||
successorMarketID={data.successorMarketID}
|
||||
parentMarketID={data.parentMarketID}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]);
|
||||
return cols;
|
||||
];
|
||||
}, [openAssetDialog]);
|
||||
|
||||
return (
|
||||
@@ -276,8 +265,9 @@ const ClosedMarketsDataGrid = ({
|
||||
rowData={rowData}
|
||||
columnDefs={colDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ SuccessorMarketRenderer, MarketNameCell }}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
components={components}
|
||||
rowHeight={45}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProductTypeShortName } from '@vegaprotocol/types';
|
||||
import type { MarketCodeCellProps } from './market-code-cell';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
describe('MarketCodeCell', () => {
|
||||
const renderComponent = (props: MarketCodeCellProps) => {
|
||||
return render(<MarketCodeCell {...props} />);
|
||||
};
|
||||
|
||||
it('renders SCCR if the market is a successor', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: 'foo',
|
||||
successorMarketID: undefined,
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders PRNT if the market is a parent', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: undefined,
|
||||
successorMarketID: 'foo',
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).not.toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders both SCCR and PRNT if the market is both a parent and a successor', () => {
|
||||
const productType = 'Future';
|
||||
const code = 'code';
|
||||
const props = {
|
||||
value: 'code',
|
||||
data: {
|
||||
productType,
|
||||
parentMarketID: 'foo',
|
||||
successorMarketID: 'bar',
|
||||
},
|
||||
} as const;
|
||||
renderComponent(props);
|
||||
expect(screen.getByTestId('stack-cell-primary')).toHaveTextContent(code);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
ProductTypeShortName[productType]
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
expect(screen.getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'SCCR'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import compact from 'lodash/compact';
|
||||
import type { ProductType } from '@vegaprotocol/types';
|
||||
import { ProductTypeMapping, ProductTypeShortName } from '@vegaprotocol/types';
|
||||
import { StackedCell } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export interface MarketCodeCellProps {
|
||||
value: string | undefined; // market code
|
||||
data: {
|
||||
productType: ProductType | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
successorMarketID: string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export const MarketCodeCell = ({ value, data }: MarketCodeCellProps) => {
|
||||
if (!value || !data || !data.productType) return null;
|
||||
|
||||
const infoSpanClasses =
|
||||
'mr-1 pr-1 uppercase border-r last:pr-0 last:mr-0 last:border-r-0 border-vega-clight-200 dark:border-vega-cdark-200';
|
||||
|
||||
const info = compact([
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="productType"
|
||||
title={ProductTypeMapping[data.productType]}
|
||||
>
|
||||
{ProductTypeShortName[data.productType]}
|
||||
</span>,
|
||||
data.parentMarketID && (
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="successor"
|
||||
title={t('Successor of a market')}
|
||||
>
|
||||
{t('SCCR')}
|
||||
</span>
|
||||
),
|
||||
data.successorMarketID && (
|
||||
<span
|
||||
className={infoSpanClasses}
|
||||
key="parent"
|
||||
title={t('Parent of a market')}
|
||||
>
|
||||
{t('PRNT')}
|
||||
</span>
|
||||
),
|
||||
]);
|
||||
|
||||
return <StackedCell primary={value} secondary={info} />;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
|
||||
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
};
|
||||
|
||||
type Props = TypedDataAgGrid<MarketMaybeWithData>;
|
||||
|
||||
export const MarketListTable = (props: Props) => {
|
||||
const columnDefs = useColumnDefs();
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
getRowId={getRowId}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
components={components}
|
||||
rowHeight={45}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketListTable;
|
||||
+27
@@ -9,14 +9,21 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { DApp, EXPLORER_MARKET, useLinks } from '@vegaprotocol/environment';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const MarketActionsDropdown = ({
|
||||
marketId,
|
||||
assetId,
|
||||
successorMarketID,
|
||||
parentMarketID,
|
||||
}: {
|
||||
marketId: string;
|
||||
assetId: string;
|
||||
successorMarketID: string | null | undefined;
|
||||
parentMarketID: string | null | undefined;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const open = useAssetDetailsDialogStore((store) => store.open);
|
||||
const linkCreator = useLinks(DApp.Explorer);
|
||||
|
||||
@@ -42,6 +49,26 @@ export const MarketActionsDropdown = ({
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View settlement asset details')}
|
||||
</TradingDropdownItem>
|
||||
{parentMarketID && (
|
||||
<TradingDropdownItem
|
||||
onClick={() => {
|
||||
navigate(Links[Routes.MARKET](parentMarketID));
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EYE} size={16} />
|
||||
{t('View parent market')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
{successorMarketID && (
|
||||
<TradingDropdownItem
|
||||
onClick={() => {
|
||||
navigate(Links[Routes.MARKET](successorMarketID));
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EYE} size={16} />
|
||||
{t('View successor market')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
</ActionsDropdown>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Tab,
|
||||
TradingAnchorButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets } from './markets';
|
||||
import { OpenMarkets } from './open-markets';
|
||||
import { Proposed } from './proposed';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { Closed } from './closed';
|
||||
@@ -33,7 +33,7 @@ export const MarketsPage = () => {
|
||||
<div className="h-full my-1 border rounded-sm border-default">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
<OpenMarkets />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { MarketsContainer } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Markets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
return (
|
||||
<MarketsContainer
|
||||
onSelect={handleOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
|
||||
import { marketListProvider } from '@vegaprotocol/markets';
|
||||
import { useEffect } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import MarketListTable from './market-list-table';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { useYesterday } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const POLLING_TIME = 2000;
|
||||
|
||||
export const OpenMarkets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
const yesterday = useYesterday();
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider: marketListProvider,
|
||||
variables: {
|
||||
since: new Date(yesterday).toISOString(),
|
||||
interval: Interval.INTERVAL_I1H,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<MarketListTable
|
||||
rowData={data}
|
||||
onCellClicked={({
|
||||
data,
|
||||
column,
|
||||
event,
|
||||
}: CellClickedEvent<MarketMaybeWithData>) => {
|
||||
if (!data) return;
|
||||
|
||||
// prevent navigating to the market page if any of the below cells are clicked
|
||||
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
|
||||
const colId = column.getColId();
|
||||
|
||||
if (
|
||||
[
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
'market-actions',
|
||||
].includes(colId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore metaKey exists
|
||||
handleOnSelect(data.id, event ? event.metaKey : false);
|
||||
}}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
getMatchingOracleProvider,
|
||||
getVerifiedStatusIcon,
|
||||
useOracleProofs,
|
||||
} from '@vegaprotocol/markets';
|
||||
|
||||
export const OracleStatus = ({
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
}: Pick<
|
||||
Market['tradableInstrument']['instrument']['product'],
|
||||
'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination'
|
||||
>) => {
|
||||
const { ORACLE_PROOFS_URL } = useEnvironment();
|
||||
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
|
||||
|
||||
if (providers) {
|
||||
const settlementDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForSettlementData.data,
|
||||
providers
|
||||
);
|
||||
const tradingTerminationDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForTradingTermination.data,
|
||||
providers
|
||||
);
|
||||
let maliciousOracleProvider = null;
|
||||
|
||||
if (settlementDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = settlementDataProvider;
|
||||
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = tradingTerminationDataProvider;
|
||||
}
|
||||
|
||||
if (!maliciousOracleProvider) return null;
|
||||
|
||||
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
|
||||
|
||||
return <Icon size={3} name={icon as IconName} className="ml-1" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMarketsMapProvider } from '@vegaprotocol/markets';
|
||||
|
||||
export const ParentMarketCell = ({
|
||||
value,
|
||||
}: {
|
||||
value: string; // parentMarketId
|
||||
}) => {
|
||||
const { data, loading } = useMarketsMapProvider();
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
if (!data || !data[value]) return <span>-</span>;
|
||||
|
||||
return <div>{data[value].tradableInstrument.instrument.code}</div>;
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import { ParentMarketCell } from './parent-market-cell';
|
||||
|
||||
const cellRenderers = {
|
||||
ParentMarketCell,
|
||||
};
|
||||
|
||||
export const Proposed = () => {
|
||||
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
|
||||
return <ProposalsList cellRenderers={cellRenderers} />;
|
||||
};
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import {
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
|
||||
const mockSuccessorsQuery = [
|
||||
{
|
||||
id: 'market1',
|
||||
parentMarketID: 'parentMarket1',
|
||||
successorMarketID: 'successorMarket1',
|
||||
},
|
||||
{ id: 'market2', parentMarketID: 'parentMarket2' },
|
||||
{ id: 'market3', successorMarketID: 'successorMarket3' },
|
||||
];
|
||||
const parentMarket1 = {
|
||||
id: 'parentMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 1', id: '1' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket1 = {
|
||||
id: 'successorMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 1', id: '2' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const parentMarket2 = {
|
||||
id: 'parentMarket2',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 2', id: '3' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket3 = {
|
||||
id: 'successorMarket3',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 3', id: '4' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
|
||||
const mockMarkets = [
|
||||
parentMarket1,
|
||||
successorMarket1,
|
||||
parentMarket2,
|
||||
successorMarket3,
|
||||
];
|
||||
|
||||
const mockClickHandler = jest.fn();
|
||||
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
|
||||
useMarketClickHandler: jest.fn().mockImplementation(() => mockClickHandler),
|
||||
}));
|
||||
|
||||
const marketMock = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
variables: undefined,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockMarkets.map((item) => ({
|
||||
node: {
|
||||
...createMarketFragment(item),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const successorMock = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockSuccessorsQuery.map((item) => ({
|
||||
node: {
|
||||
...item,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mocks = [marketMock, successorMock];
|
||||
|
||||
describe('SuccessorMarketRenderer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should properly rendered successor market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered parent market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered only parent market', async () => {
|
||||
const successorValue = 'market2';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket2', false);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
it('should properly rendered only successor market', async () => {
|
||||
const successorValue = 'market3';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 3')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket3', false);
|
||||
});
|
||||
|
||||
await act(() => {
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { MarketNameCell } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketProvider, useSuccessorMarketIds } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const SuccessorMarketRenderer = ({
|
||||
value,
|
||||
parent,
|
||||
}: {
|
||||
value: string;
|
||||
parent?: boolean;
|
||||
}) => {
|
||||
const successors = useSuccessorMarketIds(value);
|
||||
const onMarketClick = useMarketClickHandler();
|
||||
|
||||
const lookupValue = successors
|
||||
? parent
|
||||
? successors.parentMarketID
|
||||
: successors.successorMarketID
|
||||
: '';
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
variables: {
|
||||
marketId: lookupValue || '',
|
||||
},
|
||||
skip: !lookupValue,
|
||||
});
|
||||
|
||||
return data ? (
|
||||
<MarketNameCell
|
||||
value={data.tradableInstrument.instrument.code}
|
||||
data={data}
|
||||
onMarketClick={onMarketClick}
|
||||
productType={data.tradableInstrument.instrument?.product.__typename}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,226 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef, ValueFormatterParams } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type {
|
||||
MarketMaybeWithData,
|
||||
MarketMaybeWithDataAndCandles,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
import { calcCandleVolume } from '@vegaprotocol/markets';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = () => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
flex: 2,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.code'
|
||||
>) => (
|
||||
<MarketCodeCell
|
||||
value={value}
|
||||
data={{
|
||||
productType:
|
||||
data?.tradableInstrument.instrument.product.__typename,
|
||||
successorMarketID: data?.successorMarketID,
|
||||
parentMarketID: data?.parentMarketID,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
flex: 2,
|
||||
},
|
||||
{
|
||||
headerName: t('Trading mode'),
|
||||
field: 'tradingMode',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return '-';
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
|
||||
const withTriggerInfo =
|
||||
marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED;
|
||||
|
||||
if (withTriggerInfo) {
|
||||
return (
|
||||
<Tooltip
|
||||
description={`${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`}
|
||||
>
|
||||
<span>
|
||||
{Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketTradingModeMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketStateMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'data.markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
|
||||
},
|
||||
{
|
||||
headerName: t('24h volume'),
|
||||
type: 'rightAligned',
|
||||
field: 'data.candles',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithDataAndCandles>) => {
|
||||
if (!data) return 0;
|
||||
const candles = data?.candles;
|
||||
const vol = candles ? calcCandleVolume(candles) : '0';
|
||||
return Number(vol);
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: ValueFormatterParams<MarketMaybeWithDataAndCandles, 'candles'>) => {
|
||||
const candles = data?.candles;
|
||||
const vol = candles ? calcCandleVolume(candles) : '0';
|
||||
const volume =
|
||||
data && vol && vol !== '0'
|
||||
? addDecimalsFormatNumber(vol, data.positionDecimalPlaces)
|
||||
: '0.00';
|
||||
return volume;
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value =
|
||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||
return value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value.symbol}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Spread'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
filter: 'agNumberColumnFilter',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
if (!data || !data.data?.bestOfferPrice || !data.data?.bestBidPrice) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const offer = toBigNum(data.data.bestOfferPrice, data.decimalPlaces);
|
||||
const bid = toBigNum(data.data.bestBidPrice, data.decimalPlaces);
|
||||
|
||||
const spread = offer.minus(bid).toNumber();
|
||||
|
||||
// The calculation above can result in '-0' being rendered after formatting
|
||||
// so return Math.abs to remove it and just render '0'
|
||||
if (spread === 0) {
|
||||
return Math.abs(spread);
|
||||
}
|
||||
|
||||
return spread;
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) => {
|
||||
if (!value) return '-';
|
||||
return value.toString();
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'market-actions',
|
||||
field: 'id',
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
successorMarketID={data.successorMarketID}
|
||||
parentMarketID={data.parentMarketID}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export const THROTTLE_UPDATE_TIME = 500;
|
||||
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { SortType } from './sort-dropdown';
|
||||
import { SortTypeMapping } from './sort-dropdown';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { subDays } from 'date-fns';
|
||||
import { isMarketActive } from './use-market-selector-list';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
isMarketActive,
|
||||
useMarketSelectorList,
|
||||
} from './use-market-selector-list';
|
||||
import { Product } from '../../components/market-selector/product-selector';
|
||||
import { useMarketSelectorList } from './use-market-selector-list';
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
import { Product } from './product-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import type { Filter } from '../../components/market-selector';
|
||||
import type { Filter } from './market-selector';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { Product } from './product-selector';
|
||||
|
||||
// Used for sort order and filter
|
||||
const MARKET_TEMPLATE = [
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketState.STATE_SUSPENDED,
|
||||
MarketState.STATE_PENDING,
|
||||
];
|
||||
import { isMarketActive } from '../../lib/utils';
|
||||
|
||||
export const useMarketSelectorList = ({
|
||||
product,
|
||||
@@ -87,7 +80,3 @@ export const useMarketSelectorList = ({
|
||||
|
||||
return { markets, data, loading, error, reload };
|
||||
};
|
||||
|
||||
export const isMarketActive = (state: MarketState) => {
|
||||
return MARKET_TEMPLATE.includes(state);
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('Navbar', () => {
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
[expect.stringContaining('governance'), 'Governance'],
|
||||
];
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
@@ -88,6 +89,7 @@ describe('Navbar', () => {
|
||||
['/markets/all', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
[expect.stringContaining('governance'), 'Governance'],
|
||||
];
|
||||
const links = menu.getAllByRole('link');
|
||||
links.forEach((link, i) => {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { ButtonHTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEnvironment, DocsLinks, Networks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
Networks,
|
||||
DApp,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
@@ -59,14 +65,14 @@ export const Navbar = ({
|
||||
<VLogo className="w-4" />
|
||||
</NavLink>
|
||||
{/* Left section */}
|
||||
<div className="lg:hidden flex items-center">{children}</div>
|
||||
<div className="flex items-center lg:hidden">{children}</div>
|
||||
{/* Used to show header in nav on mobile */}
|
||||
<div className="hidden lg:block">
|
||||
<NavbarMenu onClick={() => setMenu(null)} />
|
||||
</div>
|
||||
|
||||
{/* Right section */}
|
||||
<div className="ml-auto flex justify-end items-center gap-2">
|
||||
<div className="flex items-center justify-end ml-auto gap-2">
|
||||
<ProtocolUpgradeCountdown />
|
||||
<NavbarMobileButton
|
||||
onClick={() => {
|
||||
@@ -100,7 +106,7 @@ export const Navbar = ({
|
||||
onOpenChange={(open) => setMenu((x) => (open ? x : null))}
|
||||
>
|
||||
<D.Overlay
|
||||
className="lg:hidden fixed inset-0 dark:bg-black/80 bg-black/50 z-20"
|
||||
className="fixed inset-0 z-20 lg:hidden dark:bg-black/80 bg-black/50"
|
||||
data-testid="navbar-menu-overlay"
|
||||
/>
|
||||
<D.Content
|
||||
@@ -111,7 +117,7 @@ export const Navbar = ({
|
||||
)}
|
||||
data-testid="navbar-menu-content"
|
||||
>
|
||||
<div className="flex justify-end items-center h-10 p-1">
|
||||
<div className="flex items-center justify-end h-10 p-1">
|
||||
<NavbarMobileButton onClick={() => setMenu(null)}>
|
||||
<span className="sr-only">{t('Close menu')}</span>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={24} />
|
||||
@@ -179,6 +185,11 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
|
||||
{t('Portfolio')}
|
||||
</NavbarLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarLinkExternal to={useLinks(DApp.Token)()}>
|
||||
{t('Governance')}
|
||||
</NavbarLinkExternal>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NavbarTrigger>{t('Resources')}</NavbarTrigger>
|
||||
<NavbarContent data-testid="navbar-content-resources">
|
||||
@@ -336,7 +347,7 @@ const NavbarLinkExternal = ({
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames(
|
||||
'flex lg:inline-flex gap-2 justify-between items-center relative',
|
||||
'flex gap-2 lg:h-full items-center',
|
||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||
'hover:text-vega-clight-100 dark:hover:text-vega-cdark-100'
|
||||
)}
|
||||
@@ -344,7 +355,7 @@ const NavbarLinkExternal = ({
|
||||
target="_blank"
|
||||
>
|
||||
<span>{children}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={14} />
|
||||
</NavLink>
|
||||
</N.Link>
|
||||
);
|
||||
@@ -364,8 +375,8 @@ const BurgerIcon = () => (
|
||||
|
||||
const NavbarListDivider = () => {
|
||||
return (
|
||||
<div className="py-2 px-6 lg:px-0" role="separator">
|
||||
<div className="h-px lg:h-full w-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||
<div className="px-6 py-2 lg:px-0" role="separator">
|
||||
<div className="w-full h-px lg:h-full lg:w-px bg-vega-clight-500 dark:bg-vega-cdark-500" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { Telemetry } from './telemetry';
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
|
||||
export const Telemetry = () => {
|
||||
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [closeTelemetry, removeToast]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[onApprovalClose, setTelemetryValue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isTelemetryNeeded && onboardingDissmissed) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [
|
||||
telemetryValue,
|
||||
isTelemetryNeeded,
|
||||
onboardingDissmissed,
|
||||
setToast,
|
||||
hasToast,
|
||||
onApprovalClose,
|
||||
setTelemetryApprovalAndClose,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -2,7 +2,8 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { GetStarted } from './get-started';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
|
||||
let mockStep = 1;
|
||||
jest.mock('./use-get-onboarding-step', () => ({
|
||||
@@ -44,13 +45,15 @@ describe('GetStarted', () => {
|
||||
globalThis.window.vega = undefined as unknown as Vega;
|
||||
});
|
||||
|
||||
it('renders nothing if connected', () => {
|
||||
it('renders nothing if dismissed', () => {
|
||||
useOnboardingStore.setState({ dismissed: true });
|
||||
mockStep = 0;
|
||||
const { container } = renderComponent({ pubKey: 'my-pubkey' });
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('steps should be ticked', () => {
|
||||
useOnboardingStore.setState({ dismissed: false });
|
||||
const navigatorGetter: jest.SpyInstance = jest.spyOn(
|
||||
window.navigator,
|
||||
'userAgent',
|
||||
@@ -87,6 +90,8 @@ describe('GetStarted', () => {
|
||||
screen.getByRole('button', { name: 'Ready to trade' })
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ready to trade' }));
|
||||
|
||||
mockStep = 5;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
|
||||
@@ -9,17 +9,15 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
OnboardingStep,
|
||||
useGetOnboardingStep,
|
||||
useOnboardingStore,
|
||||
} from './use-get-onboarding-step';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useSidebar, ViewType } from '../sidebar';
|
||||
import * as constants from '../constants';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
@@ -27,11 +25,8 @@ interface Props {
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const setDialogOpen = useOnboardingStore((store) => store.setDialogOpen);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
@@ -49,14 +44,14 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Deposit });
|
||||
dismiss();
|
||||
setDialogOpen(false);
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
} else if (step >= OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Ready to trade');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Order });
|
||||
setOnboardingViewed('true');
|
||||
dismiss();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,17 +70,12 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
export const GetStarted = ({ lead }: Props) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const getStartedNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
|
||||
const wrapperClasses = classNames(
|
||||
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||
@@ -94,7 +84,7 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{ 'mt-8': !lead }
|
||||
);
|
||||
|
||||
if (getStartedNeeded) {
|
||||
if (!dismissed) {
|
||||
return (
|
||||
<div className={wrapperClasses} data-testid="get-started-banner">
|
||||
{lead && <h2>{lead}</h2>}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
@@ -7,6 +9,29 @@ import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding';
|
||||
export const useOnboardingStore = create<{
|
||||
dialogOpen: boolean;
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
setDialogOpen: (isOpen: boolean) => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dialogOpen: true,
|
||||
dismissed: false,
|
||||
dismiss: () => set({ dismissed: true }),
|
||||
setDialogOpen: (isOpen) => set({ dialogOpen: isOpen }),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
partialize: (state) => ({
|
||||
dismissed: state.dismissed,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export enum OnboardingStep {
|
||||
ONBOARDING_UNKNOWN_STEP,
|
||||
ONBOARDING_WALLET_STEP,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { GetStarted } from './get-started';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
import { useTopTradedMarkets } from '../../lib/hooks/use-top-traded-markets';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const setOnboardingDialog = useOnboardingStore(
|
||||
(store) => store.setDialogOpen
|
||||
);
|
||||
|
||||
const { data } = useTopTradedMarkets();
|
||||
const marketId = data && data[0]?.id;
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKETS]();
|
||||
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
? t('Start trading on the worlds most advanced decentralised exchange.')
|
||||
@@ -25,7 +27,7 @@ export const WelcomeDialogContent = () => {
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row gap-8">
|
||||
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
|
||||
<div className="flex flex-col justify-between pt-3 sm:w-1/2">
|
||||
<ul className="ml-0">
|
||||
<ListItemContent
|
||||
icon={<NonCustodialIcon />}
|
||||
@@ -47,15 +49,16 @@ export const WelcomeDialogContent = () => {
|
||||
)}
|
||||
/>
|
||||
</ul>
|
||||
<TradingButton
|
||||
onClick={browseMarkets}
|
||||
<TradingAnchorButton
|
||||
href={link}
|
||||
onClick={() => setOnboardingDialog(false)}
|
||||
className="block w-full"
|
||||
data-testid="browse-markets-button"
|
||||
>
|
||||
{t('Browse the markets')}
|
||||
</TradingButton>
|
||||
{t('Explore')}
|
||||
</TradingAnchorButton>
|
||||
</div>
|
||||
<div className="sm:w-1/2 flex grow">
|
||||
<div className="flex sm:w-1/2 grow">
|
||||
<GetStarted lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,10 +75,10 @@ const ListItemContent = ({
|
||||
text: string;
|
||||
}) => {
|
||||
return (
|
||||
<li className="my-4 flex gap-3">
|
||||
<div className="shrink-0 pt-1">{icon}</div>
|
||||
<li className="flex my-4 gap-3">
|
||||
<div className="pt-1 shrink-0">{icon}</div>
|
||||
<div>
|
||||
<h3 className="text-lg leading-snug mb-2">{title}</h3>
|
||||
<h3 className="mb-2 text-lg leading-snug">{title}</h3>
|
||||
<p className="text-sm text-secondary">{text}</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,133 +1,32 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import {
|
||||
useGetOnboardingStep,
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import * as constants from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback } from 'react';
|
||||
import { useOnboardingStore } from './use-get-onboarding-step';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
|
||||
export const useOnboardingStore = create<{
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dismissed: false,
|
||||
dismiss: () => set(() => ({ dismissed: true })),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const isTelemetryPopupNeeded =
|
||||
isTelemetryNeeded &&
|
||||
(onBoardingViewed === 'true' ||
|
||||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
|
||||
const dialogOpen = useOnboardingStore((store) => store.dialogOpen);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP &&
|
||||
!dismissed;
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onClose = () => {
|
||||
if (isTelemetryPopupNeeded) {
|
||||
closeTelemetry();
|
||||
} else {
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [removeToast, closeTelemetry]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[setTelemetryValue, onApprovalClose]
|
||||
);
|
||||
|
||||
if (isTelemetryPopupNeeded) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{VEGA_ENV}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return isOnboardingDialogNeeded ? (
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
title={title}
|
||||
open={dismissed ? false : dialogOpen}
|
||||
title={
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{VEGA_ENV}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
size="medium"
|
||||
onChange={onClose}
|
||||
onChange={() => dismiss()}
|
||||
intent={Intent.None}
|
||||
dataTestId="welcome-dialog"
|
||||
>
|
||||
<WelcomeDialogContent />
|
||||
</Dialog>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const useMarketClickHandler = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
const { marketId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const isMarketPage = pathname.match(/^\/markets\/(.+)/);
|
||||
|
||||
return useCallback(
|
||||
(selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.MARKET](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else if (selectedId !== marketId || !isMarketPage) {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
},
|
||||
[navigate, marketId, replace, isMarketPage]
|
||||
);
|
||||
return (selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.MARKET](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const useMarketLiquidityClickHandler = () => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { isMarketActive } from '../utils';
|
||||
|
||||
export const useTopTradedMarkets = () => {
|
||||
const { data, loading, error } = useMarketList();
|
||||
|
||||
const activeMarkets = data?.filter((m) => isMarketActive(m.state));
|
||||
const marketsByTopTraded = data
|
||||
? orderBy(activeMarkets, (m) => calcTradedFactor(m), 'desc')
|
||||
: undefined;
|
||||
return { data: marketsByTopTraded, loading, error };
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
// Used for sort order and filter
|
||||
const MARKET_TEMPLATE = [
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketState.STATE_SUSPENDED,
|
||||
MarketState.STATE_PENDING,
|
||||
];
|
||||
|
||||
export const isMarketActive = (state: MarketState) => {
|
||||
return MARKET_TEMPLATE.includes(state);
|
||||
};
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { NavHeader } from '../components/navbar/nav-header';
|
||||
import { Routes as AppRoutes } from './client-router';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -125,6 +126,7 @@ function AppBody({ Component }: AppProps) {
|
||||
<InitializeHandlers />
|
||||
<MaybeConnectEagerly />
|
||||
<PartyData />
|
||||
<Telemetry />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,9 @@ html [data-theme='light'] {
|
||||
|
||||
--pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
|
||||
|
||||
--pennant-candlestick-inner-padding: 0.1;
|
||||
/* reduce space between candles */
|
||||
--pennant-candlestick-inner-padding: 0.175;
|
||||
--pennant-candlestick-stroke-width: 0.5;
|
||||
}
|
||||
|
||||
html [data-theme='light'] {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CandlestickChart } from 'pennant';
|
||||
import { VegaDataSource } from './data-source';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
@@ -22,8 +23,25 @@ export const CandlesChartContainer = ({
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { theme } = useThemeSwitcher();
|
||||
|
||||
const { interval, chartType, overlays, studies, merge } =
|
||||
useCandlesChartSettings();
|
||||
const {
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
} = useCandlesChartSettings();
|
||||
|
||||
const handlePaneChange = useMemo(
|
||||
() =>
|
||||
debounce((sizes: number[]) => {
|
||||
// first number is main pain, which is greedy so we don't store it
|
||||
setStudySizes(sizes.filter((_, i) => i !== 0));
|
||||
}, 300),
|
||||
[setStudySizes]
|
||||
);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return new VegaDataSource(client, marketId, pubKey);
|
||||
@@ -45,15 +63,16 @@ export const CandlesChartContainer = ({
|
||||
initialNumCandlesToDisplay: Math.floor(
|
||||
width * CANDLES_TO_WIDTH_FACTOR
|
||||
),
|
||||
studySize: 150, // default size
|
||||
studySizes,
|
||||
}}
|
||||
interval={interval}
|
||||
theme={theme}
|
||||
onOptionsChanged={(options) => {
|
||||
merge({
|
||||
overlays: options.overlays,
|
||||
studies: options.studies,
|
||||
});
|
||||
setStudies(options.studies);
|
||||
setOverlays(options.overlays);
|
||||
}}
|
||||
onPaneChanged={handlePaneChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,36 +5,45 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
type StudySizes = { [S in Study]?: number };
|
||||
|
||||
interface StoredSettings {
|
||||
interval: Interval;
|
||||
type: ChartType;
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 100;
|
||||
const STUDY_ORDER: Study[] = [
|
||||
Study.FORCE_INDEX,
|
||||
Study.RELATIVE_STRENGTH_INDEX,
|
||||
Study.ELDAR_RAY,
|
||||
Study.MACD,
|
||||
Study.VOLUME,
|
||||
];
|
||||
|
||||
const DEFAULT_CHART_SETTINGS = {
|
||||
interval: Interval.I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
};
|
||||
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
StoredSettings & {
|
||||
merge: (settings: Partial<StoredSettings>) => void;
|
||||
setType: (type: ChartType) => void;
|
||||
setInterval: (interval: Interval) => void;
|
||||
setOverlays: (overlays: Overlay[]) => void;
|
||||
setStudies: (studies: Study[]) => void;
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
immer((set) => ({
|
||||
...DEFAULT_CHART_SETTINGS,
|
||||
merge: (settings: Partial<StoredSettings>) =>
|
||||
set((state) => {
|
||||
Object.assign(state, settings);
|
||||
}),
|
||||
setType: (type) =>
|
||||
set((state) => {
|
||||
state.type = type;
|
||||
@@ -43,14 +52,35 @@ export const useCandlesChartSettingsStore = create<
|
||||
set((state) => {
|
||||
state.interval = interval;
|
||||
}),
|
||||
setOverlays: (overlays) =>
|
||||
setOverlays: (overlays) => {
|
||||
if (!overlays) return;
|
||||
|
||||
set((state) => {
|
||||
state.overlays = overlays;
|
||||
}),
|
||||
setStudies: (studies) =>
|
||||
});
|
||||
},
|
||||
setStudies: (studies) => {
|
||||
if (!studies) return;
|
||||
|
||||
// Make sure studies are always returned in the same order
|
||||
studies.sort((a, b) => {
|
||||
return STUDY_ORDER.indexOf(a) - STUDY_ORDER.indexOf(b);
|
||||
});
|
||||
|
||||
set((state) => {
|
||||
state.studies = studies;
|
||||
}),
|
||||
});
|
||||
},
|
||||
setStudySizes: (sizes) => {
|
||||
set((state) => {
|
||||
// for every study find the corresonding size and update
|
||||
// the size record for that study
|
||||
state.studies.forEach((s, i) => {
|
||||
const size = sizes[i];
|
||||
state.studySizes[s] = size;
|
||||
});
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: 'vega_candles_chart_store',
|
||||
@@ -85,15 +115,22 @@ export const useCandlesChartSettings = () => {
|
||||
[Study.VOLUME]
|
||||
);
|
||||
|
||||
// find the study size
|
||||
const studySizes = studies.map((s) => {
|
||||
const size = settings.studySizes[s] || STUDY_SIZE;
|
||||
return size;
|
||||
});
|
||||
|
||||
return {
|
||||
interval,
|
||||
chartType,
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
setInterval: settings.setInterval,
|
||||
setType: settings.setType,
|
||||
setStudies: settings.setStudies,
|
||||
setOverlays: settings.setOverlays,
|
||||
merge: settings.merge,
|
||||
setStudySizes: settings.setStudySizes,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -61,10 +61,15 @@ export function addVegaWalletConnect() {
|
||||
});
|
||||
}
|
||||
|
||||
const onboardingViewedState = { state: { dismissed: true }, version: 0 };
|
||||
|
||||
export function addSetVegaWallet() {
|
||||
Cypress.Commands.add('setVegaWallet', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
'vega_onboarding',
|
||||
JSON.stringify(onboardingViewedState)
|
||||
);
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
@@ -82,7 +87,10 @@ export function addSetVegaWallet() {
|
||||
export function addSetOnBoardingViewed() {
|
||||
Cypress.Commands.add('setOnBoardingViewed', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
'vega_onboarding',
|
||||
JSON.stringify(onboardingViewedState)
|
||||
);
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ export * from './lib/cells/centered-grid-cell';
|
||||
export * from './lib/cells/market-name-cell';
|
||||
export * from './lib/cells/order-type-cell';
|
||||
export * from './lib/cells/size';
|
||||
export * from './lib/cells/stacked-cell';
|
||||
|
||||
export * from './lib/filters/date-range-filter';
|
||||
export * from './lib/filters/set-filter';
|
||||
|
||||
@@ -59,7 +59,7 @@ export const MarketNameCell = ({
|
||||
(data as { market: Market })?.market?.tradableInstrument.instrument.product
|
||||
.__typename;
|
||||
|
||||
if (!value) return;
|
||||
if (!value) return null;
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code" data-market-id={id}>
|
||||
@@ -72,7 +72,7 @@ export const MarketNameCell = ({
|
||||
<button
|
||||
onClick={handleOnClick}
|
||||
tabIndex={0}
|
||||
className="block text-left text-ellipsis overflow-hidden whitespace-nowrap w-full"
|
||||
className="block w-full overflow-hidden text-left text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
|
||||
@@ -11,8 +11,7 @@ import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion';
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
|
||||
import {
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
@@ -24,7 +23,13 @@ import {
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { KeyValue } from './key-value';
|
||||
import { TOOLTIP_TRIGGER_CLASS_NAME } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionChevron,
|
||||
AccordionPanel,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
@@ -246,31 +251,56 @@ export const DealTicketMarginDetails = ({
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Accordion.Root type="single" collapsible>
|
||||
<Accordion.Item value="margin">
|
||||
<KeyValue
|
||||
id="margin-required"
|
||||
label={
|
||||
<Accordion.Trigger className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
||||
{t('Margin required')}
|
||||
</Accordion.Trigger>
|
||||
}
|
||||
value={formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals
|
||||
)}
|
||||
formattedValue={formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}
|
||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<Accordion.Content>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<Accordion>
|
||||
<AccordionPanel
|
||||
itemId="margin"
|
||||
trigger={
|
||||
<AccordionPrimitive.Trigger
|
||||
data-testid="accordion-toggle"
|
||||
className={classNames(
|
||||
'w-full pt-2',
|
||||
'flex items-center gap-2 text-xs',
|
||||
'group'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
data-testid={`deal-ticket-fee-margin-required`}
|
||||
key={'value-dropdown'}
|
||||
className="flex items-center gap-2 justify-between w-full"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
|
||||
<span className="text-muted">{t('Margin required')}</span>
|
||||
</Tooltip>
|
||||
|
||||
<AccordionChevron size={10} />
|
||||
</div>
|
||||
<Tooltip
|
||||
description={
|
||||
formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals
|
||||
) ?? '-'
|
||||
}
|
||||
noUnderline
|
||||
>
|
||||
<div className="font-mono text-right">
|
||||
{formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}{' '}
|
||||
{assetSymbol || ''}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</AccordionPrimitive.Trigger>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Total margin available')}
|
||||
indent
|
||||
@@ -310,12 +340,12 @@ export const DealTicketMarginDetails = ({
|
||||
quantum
|
||||
)}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
{projectedMargin}
|
||||
<KeyValue
|
||||
label={t('Liquidation price estimate')}
|
||||
label={t('Liquidation')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
symbol={quoteName}
|
||||
@@ -329,6 +359,6 @@ export const DealTicketMarginDetails = ({
|
||||
onClose={onAccountBreakdownDialogClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -538,7 +538,7 @@ const NotionalAndFees = ({
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
|
||||
@@ -464,7 +464,7 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
|
||||
@@ -25,11 +25,11 @@ export const KeyValue = ({
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
<button onClick={onClick} className="font-mono">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
<div className="font-mono">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
@@ -40,14 +40,14 @@ export const KeyValue = ({
|
||||
}`}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
'text-xs flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
<div className="text-muted">{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`} noUnderline>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||
[settlementAsset]
|
||||
);
|
||||
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
||||
'Estimated total margin that will cover open position, active orders and this order.'
|
||||
'Estimated total margin that will cover open positions, active orders and this order.'
|
||||
);
|
||||
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
|
||||
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||
@@ -60,7 +60,7 @@ export const EST_FEES_TOOLTIP_TEXT = t(
|
||||
);
|
||||
|
||||
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
||||
'This is a approximation to the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||
'This is an approximation (or a range) for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||
);
|
||||
|
||||
export const EST_SLIPPAGE = t(
|
||||
|
||||
+4
-2
@@ -7,12 +7,12 @@ export type DataSourceFilterFragment = { __typename?: 'Filter', key: { __typenam
|
||||
|
||||
export type DataSourceSpecFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
|
||||
|
||||
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
||||
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
||||
|
||||
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
||||
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
||||
|
||||
export const DataSourceFilterFragmentDoc = gql`
|
||||
fragment DataSourceFilter on Filter {
|
||||
@@ -55,6 +55,8 @@ export const MarketFieldsFragmentDoc = gql`
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
parentMarketID
|
||||
successorMarketID
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
|
||||
@@ -2,7 +2,6 @@ export * from './fees-breakdown';
|
||||
export * from './last-24h-price-change';
|
||||
export * from './last-24h-volume';
|
||||
export * from './market-info';
|
||||
export * from './markets-container';
|
||||
export * from './oracle-banner';
|
||||
export * from './oracle-basic-profile';
|
||||
export * from './oracle-full-profile';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './markets-container';
|
||||
export * from './market-table-actions';
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
PriceFlashCell,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { OracleStatus } from './oracle-status';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
interface MarketNameCellProps {
|
||||
value?: string;
|
||||
data?: MarketMaybeWithData;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
const MarketName = (props: MarketNameCellProps) => (
|
||||
<>
|
||||
<MarketNameCell {...props} />
|
||||
{props.data ? (
|
||||
<OracleStatus
|
||||
dataSourceSpecForSettlementData={
|
||||
props.data.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData
|
||||
}
|
||||
dataSourceSpecForTradingTermination={
|
||||
props.data.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
type Props = TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
};
|
||||
export const MarketListTable = ({
|
||||
onMarketClick,
|
||||
SuccessorMarketRenderer,
|
||||
...props
|
||||
}: Props) => {
|
||||
const columnDefs = useColumnDefs({ onMarketClick });
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
MarketName,
|
||||
...(SuccessorMarketRenderer ? { SuccessorMarketRenderer } : null),
|
||||
};
|
||||
return (
|
||||
<AgGrid
|
||||
getRowId={getRowId}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={components}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarketListTable;
|
||||
@@ -1,179 +0,0 @@
|
||||
import { render, screen, act, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as DataProviders from '@vegaprotocol/data-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketsContainer } from './markets-container';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
const SuccessorMarketRenderer = ({ value }: { value: string }) => {
|
||||
return '-';
|
||||
};
|
||||
|
||||
const market = {
|
||||
id: 'id-1',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: { settlementAsset: { id: 'assetId-1' } },
|
||||
},
|
||||
},
|
||||
decimalPlaces: 1,
|
||||
positionDecimalPlaces: 1,
|
||||
state: 'STATE_ACTIVE',
|
||||
tradingMode: 'TRADING_MODE_OPENING_AUCTION',
|
||||
data: {
|
||||
bestBidPrice: 100,
|
||||
},
|
||||
} as unknown as MarketMaybeWithData;
|
||||
|
||||
describe('MarketsContainer', () => {
|
||||
const spyOnSelect = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [market],
|
||||
};
|
||||
});
|
||||
});
|
||||
it('context menu should stay open', async () => {
|
||||
let rerenderRef: (ui: React.ReactElement) => void;
|
||||
await act(async () => {
|
||||
const { rerender } = render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
rerenderRef = rerender;
|
||||
});
|
||||
|
||||
// make sure ag grid is finished initializaing
|
||||
const rowContainer = await screen.findByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-center-cols-container'),
|
||||
});
|
||||
expect(within(rowContainer).getAllByRole('row')).toHaveLength(1);
|
||||
expect(
|
||||
screen.getByRole('rowgroup', {
|
||||
name: (_name, element) =>
|
||||
element.classList.contains('ag-pinned-right-cols-container'),
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
|
||||
// open the dropdown
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: (_name, element) =>
|
||||
(element.parentNode as Element)?.getAttribute('id') ===
|
||||
'cell-market-actions-9',
|
||||
})
|
||||
);
|
||||
|
||||
await checkDropdown();
|
||||
|
||||
// reset the mock and rerender so the component
|
||||
// updates with new data
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
.mockImplementation(() => {
|
||||
return {
|
||||
error: null,
|
||||
reload: jest.fn(),
|
||||
data: [{ ...market, state: 'STATE_PENDING' }],
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore we await the act above so rerenderRef is definitely defined
|
||||
rerenderRef(
|
||||
<MockedProvider mocks={[]}>
|
||||
<MarketsContainer onSelect={spyOnSelect} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
// make sure dropdown is still open
|
||||
await checkDropdown();
|
||||
|
||||
async function checkDropdown() {
|
||||
const dropdownContent = await screen.findByTestId(
|
||||
'market-actions-content'
|
||||
);
|
||||
expect(dropdownContent).toBeInTheDocument();
|
||||
expect(
|
||||
within(dropdownContent).getByRole('menuitem', {
|
||||
name: 'Copy Market ID',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it('SuccessorMarketRenderer should be rendered', async () => {
|
||||
const successorMarketName = 'Successor Market Name';
|
||||
const spySuccessorMarketRenderer = jest
|
||||
.fn()
|
||||
.mockReturnValue(successorMarketName);
|
||||
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarketID',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('presentation', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('id') === 'cell-successorMarketID-14',
|
||||
})
|
||||
).toHaveTextContent(successorMarketName);
|
||||
});
|
||||
|
||||
it('feature flag should hide successorMarketID column', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
|
||||
const spySuccessorMarketRenderer = jest.fn();
|
||||
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(spySuccessorMarketRenderer).not.toHaveBeenCalled();
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('successorMarketID');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
|
||||
const POLLING_TIME = 2000;
|
||||
interface MarketsContainerProps {
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
}
|
||||
|
||||
export const MarketsContainer = ({
|
||||
onSelect,
|
||||
SuccessorMarketRenderer,
|
||||
}: MarketsContainerProps) => {
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
reload();
|
||||
}, POLLING_TIME);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<MarketListTable
|
||||
rowData={data}
|
||||
onCellClicked={(cellEvent: CellClickedEvent) => {
|
||||
const { data, column, event } = cellEvent;
|
||||
// prevent navigating to the market page if any of the below cells are clicked
|
||||
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
|
||||
const colId = column.getColId();
|
||||
if (
|
||||
[
|
||||
'id',
|
||||
'tradableInstrument.instrument.code',
|
||||
'tradableInstrument.instrument.product.settlementAsset',
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
'market-actions',
|
||||
].includes(colId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
onSelect(
|
||||
(data as MarketMaybeWithData).id,
|
||||
(event as unknown as MouseEvent)?.metaKey ||
|
||||
(event as unknown as MouseEvent)?.ctrlKey
|
||||
);
|
||||
}}
|
||||
onMarketClick={onSelect}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
import { getMatchingOracleProvider, useOracleProofs } from '../../hooks';
|
||||
import type { Market } from '../../markets-provider';
|
||||
import { getVerifiedStatusIcon } from '../oracle-basic-profile';
|
||||
|
||||
export const OracleStatus = ({
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
}: Pick<
|
||||
Market['tradableInstrument']['instrument']['product'],
|
||||
'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination'
|
||||
>) => {
|
||||
const { ORACLE_PROOFS_URL } = useEnvironment();
|
||||
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
|
||||
return useMemo(() => {
|
||||
if (providers) {
|
||||
const settlementDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForSettlementData.data,
|
||||
providers
|
||||
);
|
||||
const tradingTerminationDataProvider = getMatchingOracleProvider(
|
||||
dataSourceSpecForTradingTermination.data,
|
||||
providers
|
||||
);
|
||||
let maliciousOracleProvider = null;
|
||||
if (settlementDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = settlementDataProvider;
|
||||
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
|
||||
maliciousOracleProvider = tradingTerminationDataProvider;
|
||||
}
|
||||
if (!maliciousOracleProvider) return null;
|
||||
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
|
||||
return <Icon size={3} name={icon as IconName} className="ml-1" />;
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
providers,
|
||||
dataSourceSpecForSettlementData,
|
||||
dataSourceSpecForTradingTermination,
|
||||
]);
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/*
|
||||
import { useMarketOverview } from '../../../../hooks/use-market-overview'
|
||||
import { colorByMarketMovement } from '../../../../lib/vega-colours'
|
||||
import { Sparkline } from '../../components/sparkline'
|
||||
import { VEGA_TABLE_CLASSES } from '../../components/vega-table'
|
||||
*/
|
||||
|
||||
export interface SummaryCellProps {
|
||||
value: string; // marketId
|
||||
}
|
||||
|
||||
export const SummaryCellView = ({ value }: SummaryCellProps) => {
|
||||
// const { sparkline, change, bullish } = useMarketOverview(value)
|
||||
// const color = colorByMarketMovement(bullish)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Sparkline data={sparkline} style={{ marginRight: 4 }} />*/}
|
||||
<span>{'change'}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
SummaryCellView.displayName = 'SummaryCellView';
|
||||
|
||||
export const SummaryCell = React.memo(SummaryCellView);
|
||||
SummaryCell.displayName = 'SummaryCell';
|
||||
@@ -1,205 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import compact from 'lodash/compact';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
|
||||
interface Props {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return useMemo<ColDef[]>(
|
||||
() =>
|
||||
compact([
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketName',
|
||||
cellRendererParams: { onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
field: 'tradableInstrument.instrument.name',
|
||||
},
|
||||
{
|
||||
headerName: t('Trading mode'),
|
||||
field: 'tradingMode',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||
if (!data?.data) return '-';
|
||||
const { trigger, marketTradingMode } = data.data;
|
||||
return marketTradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
trigger &&
|
||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketTradingModeMapping,
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
field: 'state',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||
},
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.MarketStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
field: 'successorMarketID',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
headerName: t('Best bid'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestBidPrice'
|
||||
>) =>
|
||||
data?.data?.bestBidPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Best offer'),
|
||||
field: 'data.bestOfferPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.data?.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.bestOfferPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'data.markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.markPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
MarketMaybeWithData,
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||
>) => {
|
||||
const value =
|
||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||
return value ? (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value.symbol}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'market-actions',
|
||||
field: 'id',
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<MarketActionsDropdown
|
||||
marketId={data.id}
|
||||
assetId={
|
||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]),
|
||||
[onMarketClick, openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
@@ -43,6 +43,7 @@ export const getVerifiedStatusIcon = (provider: Provider) => {
|
||||
const lastVerified = provider.oracle.last_verified
|
||||
? new Date(provider.oracle.last_verified)
|
||||
: new Date(provider.oracle.first_verified);
|
||||
|
||||
return {
|
||||
...getIconIntent(),
|
||||
message: t(
|
||||
@@ -112,13 +113,13 @@ export const OracleBasicProfile = ({
|
||||
<Icon size={3} name={icon as IconName} />
|
||||
</span>
|
||||
</span>
|
||||
<p className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2">
|
||||
<p className="mb-2 text-sm dark:text-vega-light-300 text-vega-dark-300">
|
||||
{message}
|
||||
</p>
|
||||
{oracleMarkets && (
|
||||
<p
|
||||
data-testid="signed-proofs"
|
||||
className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2"
|
||||
className="mb-2 text-sm dark:text-vega-light-300 text-vega-dark-300"
|
||||
>
|
||||
{t('Involved in %s %s', [
|
||||
oracleMarkets.length.toString(),
|
||||
@@ -130,9 +131,9 @@ export const OracleBasicProfile = ({
|
||||
<div className="flex flex-row gap-3">
|
||||
{links.map((link) => (
|
||||
<ExternalLink key={link.url} href={link.url} data-testid={link.url}>
|
||||
<span className="flex gap-1 items-center">
|
||||
<span className="flex items-center gap-1">
|
||||
<VegaIcon name={getLinkIcon(link.type)} />
|
||||
<span className="capitalize underline">{link.type}</span>
|
||||
<span className="underline capitalize">{link.type}</span>
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
|
||||
</span>
|
||||
</ExternalLink>
|
||||
|
||||
@@ -28,8 +28,6 @@ import {
|
||||
} from './market-utils';
|
||||
import { MarketsDocument } from './__generated__/markets';
|
||||
import type { Candle } from './market-candles-provider';
|
||||
import type { SuccessorMarketIdsQuery } from './__generated__/SuccessorMarket';
|
||||
import { SuccessorMarketIdsDocument } from './__generated__';
|
||||
|
||||
export type Market = MarketFieldsFragment;
|
||||
|
||||
@@ -240,34 +238,3 @@ export const useMarketList = () => {
|
||||
reload,
|
||||
};
|
||||
};
|
||||
|
||||
export type MarketSuccessors = {
|
||||
__typename?: 'Market';
|
||||
id: string;
|
||||
successorMarketID?: string | null;
|
||||
parentMarketID?: string | null;
|
||||
};
|
||||
const getMarketSuccessorData = (
|
||||
responseData: SuccessorMarketIdsQuery | null
|
||||
): MarketSuccessors[] | null =>
|
||||
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
|
||||
|
||||
export const marketSuccessorProvider = makeDataProvider<
|
||||
SuccessorMarketIdsQuery,
|
||||
MarketSuccessors[],
|
||||
never,
|
||||
never
|
||||
>({
|
||||
query: SuccessorMarketIdsDocument,
|
||||
getData: getMarketSuccessorData,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
export const useSuccessorMarketIds = (marketId: string) => {
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: marketSuccessorProvider,
|
||||
variables: undefined,
|
||||
skip: !marketId,
|
||||
});
|
||||
return data?.find((item) => item.id === marketId) ?? null;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,8 @@ fragment MarketFields on Market {
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
parentMarketID
|
||||
successorMarketID
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
|
||||
@@ -42,6 +42,8 @@ export const createMarketFragment = (
|
||||
close: null,
|
||||
open: null,
|
||||
},
|
||||
successorMarketID: null,
|
||||
parentMarketID: null,
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
MarketNameCell,
|
||||
ProgressBarCell,
|
||||
MarketProductPill,
|
||||
StackedCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
ButtonLink,
|
||||
@@ -40,7 +41,6 @@ import {
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionActionsDropdown } from './position-actions-dropdown';
|
||||
import { LiquidationPrice } from './liquidation-price';
|
||||
import { StackedCell } from './stacked-cell';
|
||||
|
||||
interface Props extends TypedDataAgGrid<Position> {
|
||||
onClose?: (data: Position) => void;
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
act,
|
||||
waitFor,
|
||||
getAllByRole,
|
||||
} from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import merge from 'lodash/merge';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
@@ -14,25 +8,13 @@ import { createProposalListFieldsFragment } from '../../lib/proposals-data-provi
|
||||
import type { ProposalsListQuery } from '../../lib';
|
||||
import { ProposalsListDocument } from '../../lib';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => {
|
||||
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||
return {
|
||||
...actual,
|
||||
FLAGS: {
|
||||
...actual.FLAGS,
|
||||
SUCCESSOR_MARKETS: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const successorMarketName = 'Successor Market Name';
|
||||
const spySuccessorMarketRenderer = jest
|
||||
.fn()
|
||||
.mockReturnValue(successorMarketName);
|
||||
const parentMarketName = 'Parent Market Name';
|
||||
const ParentMarketCell = () => <span>{parentMarketName}</span>;
|
||||
|
||||
describe('ProposalsList', () => {
|
||||
const rowContainerSelector = '.ag-center-cols-container';
|
||||
|
||||
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
|
||||
const defaultProposalEdges = [
|
||||
{
|
||||
@@ -81,75 +63,55 @@ describe('ProposalsList', () => {
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be properly rendered', async () => {
|
||||
const mock = createProposalsMock();
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
const container = document.querySelector('.ag-center-cols-container');
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList cellRenderers={{ ParentMarketCell }} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('some of states should be filtered out', async () => {
|
||||
const proposalNode = createProposalListFieldsFragment({
|
||||
id: 'id-1',
|
||||
state: Types.ProposalState.STATE_ENACTED,
|
||||
expect(document.querySelector(rowContainerSelector)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const mock = createProposalsMock({
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ProposalEdge',
|
||||
node: {
|
||||
...proposalNode,
|
||||
terms: {
|
||||
...proposalNode.terms,
|
||||
change: {
|
||||
...proposalNode.terms.change,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as PartialDeep<ProposalsListQuery>);
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
const container = document.querySelector('.ag-center-cols-container');
|
||||
await waitFor(() => {
|
||||
expect(container).toBeInTheDocument();
|
||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
||||
});
|
||||
const expectedHeaders = [
|
||||
'Market',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
'', // actions col
|
||||
];
|
||||
|
||||
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
|
||||
).toEqual(expectedHeaders);
|
||||
|
||||
const container = within(
|
||||
document.querySelector(rowContainerSelector) as HTMLElement
|
||||
);
|
||||
expect(container.getAllByRole('row')).toHaveLength(
|
||||
// @ts-ignore data is mocked
|
||||
mock?.result?.data.proposalsConnection.edges.length
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getAllByRole('gridcell', {
|
||||
name: (name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
container.getAllByRole('gridcell', {
|
||||
name: (_, element) =>
|
||||
element.getAttribute('col-id') ===
|
||||
'terms.change.successorConfiguration.parentMarketId',
|
||||
})[0]
|
||||
).toHaveTextContent(successorMarketName);
|
||||
).toHaveTextContent(parentMarketName);
|
||||
});
|
||||
|
||||
it('empty response should causes no data message display', async () => {
|
||||
@@ -169,58 +131,11 @@ describe('ProposalsList', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
expect(await screen.findByText('No markets')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'parentMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('feature flag should hide parent marketcolumn', async () => {
|
||||
const mockedFlags = jest.mocked(FLAGS);
|
||||
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||
const mock: MockedResponse<ProposalsListQuery> = {
|
||||
request: {
|
||||
query: ProposalsListDocument,
|
||||
variables: {
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
proposalsConnection: {
|
||||
__typename: 'ProposalsConnection',
|
||||
edges: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('columnheader', {
|
||||
name: (_name, element) => element.getAttribute('col-id') === 'market',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
screen.getAllByRole('columnheader').forEach((element) => {
|
||||
expect(element.getAttribute('col-id')).not.toEqual('parentMarket');
|
||||
});
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList cellRenderers={{ ParentMarketCell }} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(await screen.findByText('No proposed markets')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { MarketNameProposalCell, useColumnDefs } from './use-column-defs';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { useProposalsListQuery } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
|
||||
export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
data.filter((proposal) =>
|
||||
@@ -16,13 +16,19 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
].includes(proposal.state)
|
||||
);
|
||||
|
||||
const defaultColDef = {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
|
||||
interface ProposalListProps {
|
||||
SuccessorMarketRenderer: React.FC<{ value: string }>;
|
||||
cellRenderers: {
|
||||
[name: string]: FC<{ value: string; data: ProposalListFieldsFragment }>;
|
||||
};
|
||||
}
|
||||
|
||||
export const ProposalsList = ({
|
||||
SuccessorMarketRenderer,
|
||||
}: ProposalListProps) => {
|
||||
export const ProposalsList = ({ cellRenderers }: ProposalListProps) => {
|
||||
const { data } = useProposalsListQuery({
|
||||
variables: {
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
@@ -32,7 +38,7 @@ export const ProposalsList = ({
|
||||
const filteredData = getNewMarketProposals(
|
||||
removePaginationWrapper(data?.proposalsConnection?.edges)
|
||||
);
|
||||
const { columnDefs, defaultColDef } = useColumnDefs();
|
||||
const columnDefs = useColumnDefs();
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
@@ -40,8 +46,9 @@ export const ProposalsList = ({
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer, MarketNameProposalCell }}
|
||||
overlayNoRowsTemplate={t('No proposed markets')}
|
||||
components={cellRenderers}
|
||||
rowHeight={45}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
COL_DEFS,
|
||||
DateRangeFilter,
|
||||
SetFilter,
|
||||
StackedCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import compact from 'lodash/compact';
|
||||
import { useEnvironment, FLAGS } from '@vegaprotocol/environment';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -19,50 +19,15 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { ExternalLink, Pill } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ProposalProductTypeMapping,
|
||||
ProposalProductTypeShortName,
|
||||
ProductTypeMapping,
|
||||
ProductTypeShortName,
|
||||
ProposalStateMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { VoteProgress } from '../voting-progress';
|
||||
import { ProposalActionsDropdown } from '../proposal-actions-dropdown';
|
||||
|
||||
export const MarketNameProposalCell = ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.change.instrument.code'
|
||||
>) => {
|
||||
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||
const { change } = data?.terms || {};
|
||||
if (change?.__typename === 'NewMarket' && VEGA_TOKEN_URL) {
|
||||
const type = change.instrument.futureProduct?.__typename;
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code">{value as string}</span>
|
||||
{type && (
|
||||
<Pill
|
||||
size="xxs"
|
||||
className="uppercase ml-0.5"
|
||||
title={ProposalProductTypeMapping[type]}
|
||||
>
|
||||
{ProposalProductTypeShortName[type]}
|
||||
</Pill>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
if (data?.id) {
|
||||
const link = `${VEGA_TOKEN_URL}/proposals/${data.id}`;
|
||||
return <ExternalLink href={link}>{content}</ExternalLink>;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useColumnDefs = () => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
@@ -80,17 +45,36 @@ export const useColumnDefs = () => {
|
||||
headerName: t('Market'),
|
||||
field: 'terms.change.instrument.code',
|
||||
cellStyle: { lineHeight: '14px' },
|
||||
cellRenderer: 'MarketNameProposalCell',
|
||||
},
|
||||
{
|
||||
colId: 'description',
|
||||
headerName: t('Description'),
|
||||
field: 'terms.change.instrument.name',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: {
|
||||
value: string;
|
||||
data: ProposalListFieldsFragment;
|
||||
}) => {
|
||||
if (!value || !data) return '-';
|
||||
|
||||
// TODO: update when we switch to ProductConfiguration
|
||||
const productType = 'Future';
|
||||
return (
|
||||
<StackedCell
|
||||
primary={value}
|
||||
secondary={
|
||||
<span
|
||||
title={ProductTypeMapping[productType]}
|
||||
className="uppercase"
|
||||
>
|
||||
{ProductTypeShortName[productType]}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'asset',
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'terms.change.instrument.futureProduct.settlementAsset.name',
|
||||
field: 'terms.change.instrument.futureProduct.settlementAsset.symbol',
|
||||
},
|
||||
{
|
||||
colId: 'state',
|
||||
@@ -105,12 +89,10 @@ export const useColumnDefs = () => {
|
||||
set: ProposalStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
{
|
||||
headerName: t('Parent market'),
|
||||
field: 'id',
|
||||
colId: 'parentMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
cellRendererParams: { parent: true },
|
||||
field: 'terms.change.successorConfiguration.parentMarketId',
|
||||
cellRenderer: 'ParentMarketCell',
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
@@ -169,25 +151,12 @@ export const useColumnDefs = () => {
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (!data?.id) return null;
|
||||
|
||||
return <ProposalActionsDropdown id={data.id} />;
|
||||
},
|
||||
},
|
||||
]);
|
||||
}, [requiredMajorityPercentage]);
|
||||
|
||||
const defaultColDef: ColDef = useMemo(() => {
|
||||
return {
|
||||
sortable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
};
|
||||
}, []);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
columnDefs,
|
||||
defaultColDef,
|
||||
}),
|
||||
[columnDefs, defaultColDef]
|
||||
);
|
||||
return columnDefs;
|
||||
};
|
||||
|
||||
@@ -114,6 +114,9 @@ fragment NewMarketFields on NewMarket {
|
||||
lpPriceRange
|
||||
# linearSlippageFactor
|
||||
# quadraticSlippageFactor
|
||||
successorConfiguration {
|
||||
parentMarketId
|
||||
}
|
||||
}
|
||||
|
||||
fragment UpdateMarketFields on UpdateMarket {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -109,6 +109,58 @@ export const marketUpdateProposal: ProposalListFieldsFragment = {
|
||||
export const createProposalListFieldsFragment = (
|
||||
override?: PartialDeep<ProposalListFieldsFragment>
|
||||
): ProposalListFieldsFragment => {
|
||||
const newMarket = {
|
||||
decimalPlaces: 1,
|
||||
lpPriceRange: '',
|
||||
riskParameters: {
|
||||
__typename: 'SimpleRiskModel',
|
||||
params: {
|
||||
__typename: 'SimpleRiskModelParams',
|
||||
factorLong: 0,
|
||||
factorShort: 1,
|
||||
},
|
||||
},
|
||||
metadata: undefined,
|
||||
successorConfiguration: {
|
||||
__typename: 'SuccessorConfiguration',
|
||||
parentMarketId: 'xyz',
|
||||
},
|
||||
instrument: {
|
||||
code: 'ETHUSD',
|
||||
name: 'ETHUSD',
|
||||
futureProduct: {
|
||||
settlementAsset: {
|
||||
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
|
||||
name: 'tDAI TEST',
|
||||
symbol: 'tDAI',
|
||||
decimals: 1,
|
||||
quantum: '1',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
quoteName: '',
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
settlementDataProperty: '',
|
||||
tradingTerminationProperty: '',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
} as const;
|
||||
const defaultProposal: ProposalListFieldsFragment = {
|
||||
id: 'e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829',
|
||||
reference: 'injected_at_runtime',
|
||||
@@ -147,54 +199,7 @@ export const createProposalListFieldsFragment = (
|
||||
terms: {
|
||||
closingDatetime: '2022-11-15T12:44:34Z',
|
||||
enactmentDatetime: '2022-11-15T12:44:54Z',
|
||||
change: {
|
||||
decimalPlaces: 1,
|
||||
lpPriceRange: '',
|
||||
riskParameters: {
|
||||
__typename: 'SimpleRiskModel',
|
||||
params: {
|
||||
__typename: 'SimpleRiskModelParams',
|
||||
factorLong: 0,
|
||||
factorShort: 1,
|
||||
},
|
||||
},
|
||||
metadata: [],
|
||||
instrument: {
|
||||
code: 'ETHUSD',
|
||||
name: 'ETHUSD',
|
||||
futureProduct: {
|
||||
settlementAsset: {
|
||||
id: 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663',
|
||||
name: 'tDAI TEST',
|
||||
symbol: 'tDAI',
|
||||
decimals: 1,
|
||||
quantum: '1',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
quoteName: '',
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
settlementDataProperty: '',
|
||||
tradingTerminationProperty: '',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
},
|
||||
},
|
||||
__typename: 'FutureProduct',
|
||||
},
|
||||
__typename: 'InstrumentConfiguration',
|
||||
},
|
||||
__typename: 'NewMarket',
|
||||
},
|
||||
change: newMarket,
|
||||
__typename: 'ProposalTerms',
|
||||
},
|
||||
__typename: 'Proposal',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import classNames from 'classnames';
|
||||
import type { VegaIconProps } from '../icon';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
|
||||
export interface AccordionItemProps {
|
||||
@@ -24,6 +25,28 @@ export const Accordion = ({ panels, children }: AccordionProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionPanel = ({
|
||||
trigger,
|
||||
children,
|
||||
itemId,
|
||||
}: {
|
||||
trigger: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
itemId: string;
|
||||
}) => {
|
||||
return (
|
||||
<AccordionPrimitive.Item value={itemId}>
|
||||
<AccordionPrimitive.Header>{trigger}</AccordionPrimitive.Header>
|
||||
<AccordionPrimitive.Content
|
||||
className="py-3 text-sm"
|
||||
data-testid="accordion-content"
|
||||
>
|
||||
{children}
|
||||
</AccordionPrimitive.Content>
|
||||
</AccordionPrimitive.Item>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionItem = ({
|
||||
title,
|
||||
content,
|
||||
@@ -31,7 +54,7 @@ export const AccordionItem = ({
|
||||
}: AccordionPanelProps) => {
|
||||
const triggerClassNames = classNames(
|
||||
'w-full py-2',
|
||||
'flex items-center justify-between border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
||||
'flex items-center justify-between gap-2 border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
||||
'group'
|
||||
);
|
||||
return (
|
||||
@@ -41,7 +64,9 @@ export const AccordionItem = ({
|
||||
data-testid="accordion-toggle"
|
||||
className={triggerClassNames}
|
||||
>
|
||||
<span data-testid="accordion-title">{title}</span>
|
||||
<span data-testid="accordion-title" className="flex-1 text-left">
|
||||
{title}
|
||||
</span>
|
||||
<AccordionChevron aria-hidden />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
@@ -55,15 +80,17 @@ export const AccordionItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const AccordionChevron = () => {
|
||||
export const AccordionChevron = ({
|
||||
size = 16,
|
||||
}: Pick<VegaIconProps, 'size'>) => {
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
'transform transition ease-in-out duration-300',
|
||||
'flex transform transition ease-in-out duration-300',
|
||||
'group-data-[state=open]:rotate-180'
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden />
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden size={size} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export const IconEyeOff = ({ size = 16 }: { size: number }) => {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path d="M16 7.97v-.02-.01-.02-.02a.672.672 0 00-.17-.36c-.49-.63-1.07-1.2-1.65-1.72l-3.16 2.26a2.978 2.978 0 01-2.98 2.9c-.31 0-.6-.06-.88-.15L5.09 12.3c.44.19.9.36 1.37.47.97.23 1.94.24 2.92.05.88-.17 1.74-.54 2.53-.98 1.25-.7 2.39-1.67 3.38-2.75.18-.2.37-.41.53-.62.09-.1.15-.22.17-.36v-.02-.02-.01-.02-.03c.01-.02.01-.03.01-.04zm-.43-4.17c.25-.18.43-.46.43-.8 0-.55-.45-1-1-1-.22 0-.41.08-.57.2l-.01-.01-2.67 1.91c-.69-.38-1.41-.69-2.17-.87a6.8 6.8 0 00-2.91-.05c-.88.18-1.74.54-2.53.99-1.25.7-2.39 1.67-3.38 2.75-.18.2-.37.41-.53.62-.23.29-.23.63-.01.92.51.66 1.11 1.25 1.73 1.79.18.16.38.29.56.44l-2.09 1.5.01.01c-.25.18-.43.46-.43.8 0 .55.45 1 1 1 .22 0 .41-.08.57-.2l.01.01 14-10-.01-.01zm-10.41 5a3.03 3.03 0 01-.11-.8 2.99 2.99 0 012.99-2.98c.62 0 1.19.21 1.66.53L5.16 8.8z" />
|
||||
<path d="M13.5 13.9989C13.4343 13.999 13.3693 13.9861 13.3086 13.961C13.248 13.9358 13.1929 13.8989 13.1466 13.8524L2.14658 2.85237C2.05677 2.75784 2.00744 2.63197 2.00911 2.50159C2.01077 2.37121 2.06331 2.24664 2.15551 2.15443C2.24771 2.06223 2.37228 2.0097 2.50266 2.00803C2.63304 2.00636 2.75892 2.05569 2.85345 2.1455L13.8534 13.1455C13.9233 13.2154 13.9709 13.3045 13.9902 13.4015C14.0095 13.4984 13.9996 13.5989 13.9617 13.6902C13.9239 13.7816 13.8599 13.8597 13.7777 13.9146C13.6955 13.9695 13.5989 13.9989 13.5 13.9989ZM7.98939 11.9989C6.69282 11.9989 5.44251 11.6152 4.27314 10.8583C3.20845 10.1708 2.25001 9.18612 1.50126 8.01456V8.01206C2.12439 7.11925 2.80689 6.36425 3.54001 5.7555C3.54664 5.74995 3.55205 5.74309 3.5559 5.73535C3.55975 5.72761 3.56194 5.71916 3.56235 5.71052C3.56277 5.70189 3.56138 5.69326 3.55829 5.68519C3.5552 5.67712 3.55046 5.66977 3.54439 5.66362L2.92189 5.04206C2.91083 5.03091 2.89597 5.02433 2.88028 5.02363C2.86458 5.02294 2.8492 5.02818 2.8372 5.03831C2.05845 5.69456 1.33564 6.49956 0.67845 7.44206C0.565383 7.60434 0.503113 7.79658 0.499551 7.99433C0.49599 8.19209 0.551299 8.38644 0.65845 8.55269C1.48376 9.84425 2.54595 10.9321 3.7297 11.698C5.06251 12.5614 6.49689 12.9989 7.98939 12.9989C8.795 12.9964 9.59491 12.8637 10.3581 12.6058C10.3682 12.6024 10.3772 12.5965 10.3844 12.5886C10.3915 12.5807 10.3965 12.5711 10.3989 12.5608C10.4013 12.5504 10.4011 12.5396 10.3981 12.5294C10.3952 12.5192 10.3897 12.5099 10.3822 12.5024L9.70782 11.828C9.6923 11.8129 9.6731 11.802 9.65212 11.7965C9.63113 11.7911 9.60909 11.7911 9.58814 11.7967C9.06587 11.9312 8.52869 11.9992 7.98939 11.9989ZM15.3388 7.45519C14.5119 6.17644 13.4391 5.09019 12.2366 4.31362C10.9063 3.45362 9.43751 2.99894 7.98939 2.99894C7.19232 3.00035 6.40116 3.13589 5.64908 3.39987C5.63905 3.40336 5.63009 3.40934 5.62302 3.41725C5.61595 3.42516 5.61101 3.43474 5.60866 3.44509C5.60632 3.45544 5.60664 3.46621 5.60961 3.4764C5.61258 3.48658 5.6181 3.49585 5.62564 3.50331L6.29908 4.17675C6.31475 4.19216 6.33422 4.20316 6.3555 4.20865C6.37679 4.21414 6.39915 4.21391 6.42032 4.208C6.93188 4.06962 7.45945 3.99932 7.98939 3.99894C9.26095 3.99894 10.5075 4.38737 11.6941 5.15519C12.7788 5.85519 13.7485 6.83894 14.4991 7.99894C14.4996 7.99965 14.4999 8.00053 14.4999 8.00144C14.4999 8.00234 14.4996 8.00322 14.4991 8.00394C13.9542 8.86173 13.2781 9.62867 12.4953 10.2767C12.4886 10.2823 12.4831 10.2891 12.4792 10.2969C12.4753 10.3046 12.4731 10.3131 12.4727 10.3218C12.4722 10.3305 12.4736 10.3392 12.4767 10.3473C12.4798 10.3554 12.4845 10.3628 12.4906 10.3689L13.1125 10.9905C13.1235 11.0016 13.1383 11.0082 13.1539 11.0089C13.1695 11.0097 13.1849 11.0046 13.1969 10.9946C14.0325 10.291 14.7558 9.46395 15.3419 8.54206C15.4455 8.37961 15.5002 8.19083 15.4997 7.99817C15.4991 7.8055 15.4433 7.61704 15.3388 7.45519Z" />
|
||||
<path d="M8.00002 4.99777C7.77531 4.99765 7.5513 5.02281 7.33221 5.07277C7.32114 5.07507 7.31091 5.08033 7.30259 5.08798C7.29428 5.09563 7.28819 5.1054 7.28498 5.11624C7.28178 5.12707 7.28157 5.13858 7.28438 5.14952C7.2872 5.16047 7.29293 5.17045 7.30096 5.1784L10.8194 8.6959C10.8273 8.70393 10.8373 8.70966 10.8483 8.71247C10.8592 8.71529 10.8707 8.71508 10.8816 8.71187C10.8924 8.70867 10.9022 8.70258 10.9098 8.69427C10.9175 8.68595 10.9227 8.67571 10.925 8.66465C11.0252 8.22529 11.0251 7.769 10.9247 7.32968C10.8244 6.89036 10.6263 6.47929 10.3453 6.127C10.0643 5.77472 9.70756 5.49026 9.30153 5.29477C8.89551 5.09928 8.45066 4.99776 8.00002 4.99777ZM5.18065 7.29965C5.1727 7.29161 5.16272 7.28588 5.15177 7.28307C5.14083 7.28026 5.12932 7.28047 5.11849 7.28367C5.10765 7.28688 5.09788 7.29296 5.09023 7.30128C5.08257 7.3096 5.07732 7.31983 5.07502 7.3309C4.96169 7.82601 4.97592 8.34178 5.11638 8.82989C5.25684 9.318 5.51892 9.76245 5.87807 10.1216C6.23722 10.4807 6.68167 10.7428 7.16978 10.8833C7.65789 11.0237 8.17366 11.038 8.66877 10.9246C8.67984 10.9223 8.69007 10.9171 8.69839 10.9094C8.70671 10.9018 8.71279 10.892 8.716 10.8812C8.7192 10.8703 8.71941 10.8588 8.7166 10.8479C8.71379 10.8369 8.70806 10.827 8.70002 10.819L5.18065 7.29965Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Portal,
|
||||
} from '@radix-ui/react-tooltip';
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const tooltipContentClasses =
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
||||
@@ -18,10 +19,14 @@ export interface TooltipProps {
|
||||
align?: 'start' | 'center' | 'end';
|
||||
side?: 'top' | 'right' | 'bottom' | 'left';
|
||||
sideOffset?: number;
|
||||
noUnderline?: boolean;
|
||||
}
|
||||
|
||||
export const TOOLTIP_TRIGGER_CLASS_NAME =
|
||||
'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed';
|
||||
export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) =>
|
||||
classNames(
|
||||
{ 'underline underline-offset-2': !noUnderline },
|
||||
'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed'
|
||||
);
|
||||
|
||||
// Conditionally rendered tooltip if description content is provided.
|
||||
export const Tooltip = ({
|
||||
@@ -31,11 +36,12 @@ export const Tooltip = ({
|
||||
sideOffset,
|
||||
align = 'start',
|
||||
side = 'bottom',
|
||||
noUnderline,
|
||||
}: TooltipProps) =>
|
||||
description ? (
|
||||
<Provider delayDuration={200} skipDelayDuration={100}>
|
||||
<Root open={open}>
|
||||
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
||||
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(noUnderline)}>
|
||||
{children}
|
||||
</Trigger>
|
||||
{description && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { Intent } from '../../utils/intent';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
type TradingButtonProps = {
|
||||
size?: 'large' | 'medium' | 'small' | 'extra-small';
|
||||
@@ -119,30 +120,22 @@ export const TradingButton = forwardRef<
|
||||
)
|
||||
);
|
||||
|
||||
export const TradingAnchorButton = forwardRef<
|
||||
HTMLAnchorElement,
|
||||
AnchorHTMLAttributes<HTMLAnchorElement> & TradingButtonProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
size = 'medium',
|
||||
intent = Intent.None,
|
||||
icon,
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
subLabel,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<a
|
||||
ref={ref}
|
||||
href={href}
|
||||
className={getClassName({ size, subLabel, intent }, className)}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
</a>
|
||||
)
|
||||
export const TradingAnchorButton = ({
|
||||
size = 'medium',
|
||||
intent = Intent.None,
|
||||
icon,
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
subLabel,
|
||||
...props
|
||||
}: AnchorHTMLAttributes<HTMLAnchorElement> &
|
||||
TradingButtonProps & { href: string }) => (
|
||||
<Link
|
||||
to={href}
|
||||
className={getClassName({ size, subLabel, intent }, className)}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
</Link>
|
||||
);
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "13.3.0",
|
||||
"pennant": "1.11.1",
|
||||
"pennant": "1.12.0",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -20253,10 +20253,10 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pennant@1.11.1:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.11.1.tgz#f47bceaade01db215eeba666cd755840d16fe9ed"
|
||||
integrity sha512-U26OxjxETWLJAvCFj20oH0y5gzyBfJ5oD4O3dYg1aWnH4giUYDPWuUDFnzlMJ+yXolyYECccXm6S1BkoVcVzDg==
|
||||
pennant@1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.12.0.tgz#e12707d5f1aac554d81bad060637e608335e0b50"
|
||||
integrity sha512-xosg5erRf+Ke9iORdqyv+SOGcD3uJX1dgf990q1DvHuzz36w2txCZsfnvcXhRO++HYVKS9sU/YLPRLJgolFGtA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@d3fc/d3fc-technical-indicator" "^8.0.1"
|
||||
|
||||
Reference in New Issue
Block a user