fix(governance): batch proposal diff and proposal headers (#5825)

This commit is contained in:
Art 2024-02-20 15:04:11 +01:00 committed by GitHub
parent be6f395ce4
commit 48d6be0adf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 187 additions and 38 deletions

View File

@ -160,7 +160,7 @@ describe('Proposal header', () => {
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update to market ID: MarketId'
'Update to market: MarketId'
);
});

View File

@ -36,6 +36,8 @@ import { type Proposal, type BatchProposal } from '../../types';
import { type ProposalTermsFieldsFragment } from '../../__generated__/Proposals';
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import { getIndicatorStyle } from '../proposal/colours';
import { MarketName } from '../proposal/market-name';
const ProposalTypeTags = ({
proposal,
@ -144,8 +146,42 @@ const ProposalDetails = ({
terms.change?.market?.id &&
terms.change.updateType ? (
<>
{t(terms.change.updateType)}:{' '}
{truncateMiddle(terms.change.market.id)}
<span>{t(terms.change.updateType)}: </span>
<span className="inline-flex gap-2">
<span className="break-all">
<MarketName marketId={terms.change.market.id} />
</span>
<span className="inline-flex items-end gap-0">
<CopyWithTooltip
text={terms.change.market.id}
description={t('copyId')}
>
<button className="inline-block px-1">
<VegaIcon size={20} name={VegaIconNames.COPY} />
</button>
</CopyWithTooltip>
<Tooltip description={t('OpenInConsole')} align="center">
<button
className="inline-block px-1"
onClick={() => {
const marketPageLink = consoleLink(
CONSOLE_MARKET_PAGE.replace(
':marketId',
// @ts-ignore ts doesn't like this field even though its already a string above???
terms.change.market.id
)
);
window.open(marketPageLink, '_blank');
}}
>
<VegaIcon
size={20}
name={VegaIconNames.OPEN_EXTERNAL}
/>
</button>
</Tooltip>
</span>
</span>
</>
) : null}
</span>
@ -154,13 +190,15 @@ const ProposalDetails = ({
case 'UpdateMarket': {
return (
<>
<span>{t('UpdateToMarket')}:</span>{' '}
<span>{t('UpdateToMarket')}: </span>
<span className="inline-flex items-start gap-2">
<span className="break-all">{terms.change.marketId} </span>
<span className="break-all">
<MarketName marketId={terms.change.marketId} />
</span>
<span className="inline-flex items-end gap-0">
<CopyWithTooltip
text={terms.change.marketId}
description={t('copyToClipboard')}
description={t('copyId')}
>
<button className="inline-block px-1">
<VegaIcon size={20} name={VegaIconNames.COPY} />
@ -286,12 +324,15 @@ const ProposalDetails = ({
{proposal.subProposals.map((p, i) => {
if (!p?.terms) return null;
return (
<li key={i}>
<div>{renderDetails(p.terms)}</div>
<SubProposalStateText
state={proposal.state}
enactmentDatetime={p.terms.enactmentDatetime}
/>
<li key={i} className="flex gap-3">
<span className={getIndicatorStyle(i + 1)}>{i + 1}</span>
<span>
<div>{renderDetails(p.terms)}</div>
<SubProposalStateText
state={proposal.state}
enactmentDatetime={p.terms.enactmentDatetime}
/>
</span>
</li>
);
})}

View File

@ -9,6 +9,7 @@ import { useState } from 'react';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { type UpdateMarketStatesFragment } from '../../__generated__/Proposals';
import { MarketUpdateTypeMapping } from '@vegaprotocol/types';
interface ProposalUpdateMarketStateProps {
change: UpdateMarketStatesFragment | null;
@ -49,6 +50,12 @@ export const ProposalUpdateMarketState = ({
{t('marketId')}
{market?.id}
</KeyValueTableRow>
<KeyValueTableRow>
{t('State')}
<span className="bg-vega-green-650 px-1">
{MarketUpdateTypeMapping[change.updateType]}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
{t('marketName')}
{market?.tradableInstrument?.instrument?.name}

View File

@ -0,0 +1,33 @@
import classNames from 'classnames';
// rainbow-ish order
const COLOURS = ['red', 'pink', 'orange', 'yellow', 'green', 'blue', 'purple'];
const getColour = (indicator: number, max = COLOURS.length) => {
const available =
max < COLOURS.length ? COLOURS.slice(COLOURS.length - max) : COLOURS;
const tiers = Object.keys(available).length;
let index = Math.abs(indicator - 1);
if (indicator >= tiers) {
index = index % tiers;
}
return available[index];
};
export const getStyle = (indicator: number, max = COLOURS.length) =>
classNames({
'bg-vega-yellow-400': 'yellow' === getColour(indicator, max),
'bg-vega-green-400': 'green' === getColour(indicator, max),
'bg-vega-blue-400': 'blue' === getColour(indicator, max),
'bg-vega-purple-400': 'purple' === getColour(indicator, max),
'bg-vega-pink-400': 'pink' === getColour(indicator, max),
'bg-vega-orange-400': 'orange' === getColour(indicator, max),
'bg-vega-red-400': 'red' === getColour(indicator, max),
'bg-vega-clight-600': 'none' === getColour(indicator, max),
});
export const getIndicatorStyle = (indicator: number) =>
classNames(
'rounded-sm text-black inline-block px-1 py-1 font-alpha calt h-8',
getStyle(indicator)
);

View File

@ -0,0 +1,14 @@
import { useMarketInfoQuery } from '@vegaprotocol/markets';
export const MarketName = ({ marketId }: { marketId?: string }) => {
const { data } = useMarketInfoQuery({
variables: {
marketId: marketId || '',
},
skip: !marketId,
});
return (
<span>{data?.market?.tradableInstrument.instrument.code || marketId}</span>
);
};

View File

@ -12,21 +12,26 @@ import {
import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
import { getIndicatorStyle } from './colours';
export const ProposalChangeDetails = ({
proposal,
terms,
restData,
indicator,
}: {
proposal: Proposal | BatchProposal;
terms: ProposalTermsFieldsFragment;
// eslint-disable-next-line
restData: any;
indicator?: number;
}) => {
let details = null;
switch (terms.change.__typename) {
case 'NewAsset': {
if (proposal.id && terms.change.source.__typename === 'ERC20') {
return (
details = (
<div>
<ListAsset
assetId={proposal.id}
@ -37,62 +42,81 @@ export const ProposalChangeDetails = ({
</div>
);
}
return null;
break;
}
case 'UpdateAsset': {
if (proposal.id) {
return (
details = (
<ProposalAssetDetails
change={terms.change}
assetId={terms.change.assetId}
/>
);
}
return null;
break;
}
case 'NewMarket': {
if (proposal.id) {
return <ProposalMarketData proposalId={proposal.id} />;
details = <ProposalMarketData proposalId={proposal.id} />;
}
return null;
break;
}
case 'UpdateMarket': {
if (proposal.id) {
return (
const marketId = terms.change.marketId;
const proposalData = restData?.data?.proposal;
let updatedProposal = null;
// single proposal
if ('terms' in proposalData) {
updatedProposal = proposalData?.terms?.updateMarket?.changes;
}
// batch proposal - need to fish for the actual changes
if (
'batchTerms' in proposalData &&
Array.isArray(proposalData.batchTerms?.changes)
) {
updatedProposal = proposalData?.batchTerms?.changes.find(
(ch: { updateMarket?: { marketId: string } }) =>
ch?.updateMarket?.marketId === marketId
)?.updateMarket?.changes;
}
details = (
<div className="flex flex-col gap-4">
<ProposalMarketData proposalId={proposal.id} />
<ProposalMarketChanges
marketId={terms.change.marketId}
updatedProposal={
restData?.data?.proposal?.terms?.updateMarket?.changes
}
updatedProposal={updatedProposal}
/>
</div>
);
}
return null;
break;
}
case 'NewTransfer': {
if (proposal.id) {
return <ProposalTransferDetails proposalId={proposal.id} />;
details = <ProposalTransferDetails proposalId={proposal.id} />;
}
return null;
break;
}
case 'CancelTransfer': {
if (proposal.id) {
return <ProposalCancelTransferDetails proposalId={proposal.id} />;
details = <ProposalCancelTransferDetails proposalId={proposal.id} />;
}
return null;
break;
}
case 'UpdateMarketState': {
return <ProposalUpdateMarketState change={terms.change} />;
details = <ProposalUpdateMarketState change={terms.change} />;
break;
}
case 'UpdateReferralProgram': {
return <ProposalReferralProgramDetails change={terms.change} />;
details = <ProposalReferralProgramDetails change={terms.change} />;
break;
}
case 'UpdateVolumeDiscountProgram': {
return <ProposalVolumeDiscountProgramDetails change={terms.change} />;
details = <ProposalVolumeDiscountProgramDetails change={terms.change} />;
break;
}
case 'UpdateNetworkParameter': {
if (
@ -100,18 +124,27 @@ export const ProposalChangeDetails = ({
terms.change.networkParameter.key ===
'rewards.activityStreak.benefitTiers'
) {
return <ProposalUpdateBenefitTiers change={terms.change} />;
details = <ProposalUpdateBenefitTiers change={terms.change} />;
}
return null;
break;
}
case 'NewFreeform':
case 'NewSpotMarket':
case 'UpdateSpotMarket': {
return null;
}
case 'UpdateSpotMarket':
default: {
return null;
break;
}
}
if (indicator != null) {
details = (
<div className="flex gap-3 mb-3">
<div className={getIndicatorStyle(indicator)}>{indicator}</div>
<div>{details}</div>
</div>
);
}
return details;
};

View File

@ -70,6 +70,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
if (!p?.terms) return null;
return (
<ProposalChangeDetails
indicator={i + 1}
key={i}
proposal={proposal}
terms={p.terms}

View File

@ -15,6 +15,7 @@ import {
type VoteFieldsFragment,
} from '../../__generated__/Proposals';
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
import { getIndicatorStyle } from '../proposal/colours';
export const CompactVotes = ({ number }: { number: BigNumber }) => (
<CompactNumber
@ -176,6 +177,7 @@ const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
if (!p?.terms) return null;
return (
<VoteBreakdownBatchSubProposal
indicator={i + 1}
key={i}
proposal={proposal}
votes={proposal.votes}
@ -255,10 +257,12 @@ const VoteBreakdownBatchSubProposal = ({
proposal,
votes,
terms,
indicator,
}: {
proposal: BatchProposal;
votes: VoteFieldsFragment;
terms: ProposalTermsFieldsFragment;
indicator?: number;
}) => {
const { t } = useTranslation();
const voteInfo = useVoteInformation({
@ -269,9 +273,16 @@ const VoteBreakdownBatchSubProposal = ({
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
const indicatorElement = indicator && (
<span className={getIndicatorStyle(indicator)}>{indicator}</span>
);
return (
<div>
<h4>{t(terms.change.__typename)}</h4>
<div className="flex items-baseline gap-3">
{indicatorElement}
<h4>{t(terms.change.__typename)}</h4>
</div>
<VoteBreakDownUI
voteInfo={voteInfo}
isProposalOpen={isProposalOpen}

View File

@ -159,6 +159,7 @@
"ContinueSharingData": "Continue sharing data",
"copied!": "Copied!",
"copyToClipboard": "Copy to clipboard",
"copyId": "Copy ID to clipboard",
"CouldNotInstantiateMarket": "Could not instantiate market",
"created": "Created",
"CreateProposalAndDownloadJSONToShare": "Create proposal and download JSON to share",
@ -807,7 +808,7 @@
"unsupportedVersion": "Looks like you're running an outdated version of GoWallet. You're running {{version}} but {{requiredVersion}} is required.",
"UpdateAsset": "Update asset",
"UpdateAssetProposal": "Update asset proposal",
"UpdateToMarket": "Update to market ID",
"UpdateToMarket": "Update to market",
"OpenInConsole": "Open in Console",
"UpdateMarket": "Update market",
"UpdateMarketProposal": "Update market proposal",

View File

@ -8,6 +8,7 @@ import {
type PeggedReference,
type ProposalChange,
type TransferStatus,
MarketUpdateType,
} from './__generated__/types';
import type { AccountType } from './__generated__/types';
import type {
@ -755,3 +756,10 @@ export const LiquidityFeeMethodMappingDescription: {
METHOD_UNSPECIFIED: 'Unspecified',
METHOD_WEIGHTED_AVERAGE: `This liquidity fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment.`,
};
export const MarketUpdateTypeMapping = {
[MarketUpdateType.MARKET_STATE_UPDATE_TYPE_RESUME]: 'Resume',
[MarketUpdateType.MARKET_STATE_UPDATE_TYPE_SUSPEND]: 'Suspend',
[MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE]: 'Terminate',
[MarketUpdateType.MARKET_STATE_UPDATE_TYPE_UNSPECIFIED]: 'Unspecified',
};